fix: populate scope with empty values for outputs of skipped/omitted …#15932
Conversation
utafrali
left a comment
There was a problem hiding this comment.
The core logic is sound: Template.Outputs is a value type so there is no nil dereference risk, and the empty-string scope entries correctly unblock downstream when expressions that reference skipped step outputs. The main gaps are missing artifact output handling (a real failure mode for artifact references), shadowed variable names, and a silently dropped error from ResolveTemplate.
| woc.buildLocalScope(stepsCtx.scope, prefix, childNode) | ||
|
|
||
| if (childNode.Phase == wfv1.NodeSkipped || childNode.Phase == wfv1.NodeOmitted) && childNode.Outputs == nil { | ||
| _, tmpl, _, err := stepsCtx.tmplCtx.ResolveTemplate(ctx, &step) |
There was a problem hiding this comment.
:= here shadows two outer-scoped names: the tmpl parameter of executeSteps and the loop-level err. While the inner block scope keeps this from being a correctness bug, shadow linters (and code readers) will flag it. Use distinct names:
stepTmpl, resolveErr := stepsCtx.tmplCtx.ResolveTemplate(ctx, &step)
if resolveErr == nil && stepTmpl != nil {|
|
||
| if (childNode.Phase == wfv1.NodeSkipped || childNode.Phase == wfv1.NodeOmitted) && childNode.Outputs == nil { | ||
| _, tmpl, _, err := stepsCtx.tmplCtx.ResolveTemplate(ctx, &step) | ||
| if err == nil && tmpl != nil { |
There was a problem hiding this comment.
When ResolveTemplate fails the error is silently dropped and the scope remains unpopulated, so a downstream step will still fail with an unresolvable reference. The caller gets no indication that template resolution was attempted and failed. A debug log here would make production troubleshooting much easier:
if resolveErr != nil {
woc.log.WithError(resolveErr).Debug(ctx, "failed to resolve template for skipped step, outputs will not be populated in scope")
}| } | ||
| if tmpl.Outputs.Result != nil { | ||
| stepsCtx.scope.addParamToScope(fmt.Sprintf("%s.outputs.result", prefix), "") | ||
| } |
There was a problem hiding this comment.
The fix handles Parameters and Result but skips Artifacts. If any downstream step (or when expression) references steps.<skipped>.outputs.artifacts.<name>, it will still fail with an unresolvable reference. addArtifactToScope accepts a wfv1.Artifact value, so you can register a zero-value placeholder:
for _, artifact := range tmpl.Outputs.Artifacts {
key := fmt.Sprintf("%s.outputs.artifacts.%s", prefix, artifact.Name)
stepsCtx.scope.addArtifactToScope(key, wfv1.Artifact{})
}This mirrors how the parameter case is handled above.
| }) | ||
| } | ||
|
|
||
| func (s *FunctionalSuite) TestStepsSkippedOutputRef() { |
There was a problem hiding this comment.
The test exercises NodeSkipped but never NodeOmitted. Both phases are handled in the fix. Since Omitted is a distinct node phase that arises in DAG templates (not steps), it may require a separate workflow fixture, but at least a comment explaining why it isn't tested would clarify the intent.
There was a problem hiding this comment.
Hi, sorry for that, but honestly its my first time I wrote golang and did something in argo -
why it isn't tested would clarify the intent.
honestly - I just fixed the case which blocks us, that's the reason, I can try to fix other cases as well but this can take some time.
If its enough to write comment, can you please sketch some comment I can paste ? I will probably not create anything meaningful :/
There was a problem hiding this comment.
@utafrali I pushed following comment on that test
// TestStepsSkippedOutputRef tests that output references to skipped steps resolve to empty strings
// rather than causing an unresolvable reference error. NodeOmitted is not exercised here because
// that phase arises in DAG templates, not steps; the fix covers both phases in the same code path.
|
@utafrali thank you for review 🙇 - I applied what I could and tested it, I have question only regarding the comment for test. |
|
@isubasinghe is there a chance to push on that ? |
|
Hey could you please lint and rebase this? I really want to get this merged. |
|
@isubasinghe lint ? |
|
Yes please run See https://github.com/argoproj/argo-workflows/actions/runs/24570724852/job/71842877000?pr=15932 |
…steps (argoproj#15873) Signed-off-by: KowalczykBartek <bkowalczyyk@gmail.com>
Signed-off-by: KowalczykBartek <bkowalczyyk@gmail.com>
Signed-off-by: KowalczykBartek <bkowalczyyk@gmail.com>
Signed-off-by: KowalczykBartek <bkowalczyyk@gmail.com>
0ae31ea to
421e737
Compare
|
I have no idea what I just did - @isubasinghe pls check if now its ok edit: lint is green now |
|
Hmm an end to end test is broken according to the logs. |
|
@isubasinghe i wrote one test it pass not sure what i can do |
|
https://github.com/argoproj/argo-workflows/actions/runs/24951771817/job/73307331648?pr=15932 This is failing though, please do check the Github checks. Are you interacting with this via CLI or not via the Github UI? |
|
ok now I see :/ let me check |
Signed-off-by: KowalczykBartek <bkowalczyyk@gmail.com>
|
@isubasinghe do I see correctly it succeeded now or it need to be forced to run ? |
|
@isubasinghe any update ? |
| } else { | ||
| woc.buildLocalScope(stepsCtx.scope, prefix, childNode) | ||
|
|
||
| if (childNode.Phase == wfv1.NodeSkipped || childNode.Phase == wfv1.NodeOmitted) && childNode.Outputs == nil { |
There was a problem hiding this comment.
I don't quite understand why there is this nil check here.
If a node has been set to skipped or omitted, the outputs should be nil, this not being the case is arguably a mistake?
I believe the default param population happens in line 198.
There was a problem hiding this comment.
Oh no I was just asking you why you added this check.
There was a problem hiding this comment.
why you added this check.
honestly - i don't know :) I just wanted to craft something asap, i have little domain knowledge around that codebase, but you are probably right, if node is skipped or omitted it doesn't matter what is the output we will override it.
| woc.log.WithError(resolveErr).Debug(ctx, "failed to resolve template for skipped step, outputs will not be populated in scope") | ||
| } | ||
|
|
||
| if resolveErr == nil && tmpl != nil { |
There was a problem hiding this comment.
Did you mean to use stepTmpl here?
| stepsCtx.scope.addParamToScope(key, "") | ||
| } | ||
| // register a zero-value placeholder for artifacts | ||
| for _, artifact := range tmpl.Outputs.Artifacts { |
There was a problem hiding this comment.
Same here, did you mean to use stepTmpl?
| } else if val == "" && param.ValueFrom.Default != nil { | ||
| // A skipped/omitted step contributes "" to scope; treat that as missing and use the default | ||
| val = param.ValueFrom.Default.String() | ||
| } |
There was a problem hiding this comment.
Revert this change, val could validly be "", we shouldn't override it in that case.
There was a problem hiding this comment.
Okay I think this means we need a way to determine "" from this yielded "" and this was omitted/skipped.
There was a problem hiding this comment.
so I reversed it - is that ok ?
Signed-off-by: KowalczykBartek <bkowalczyyk@gmail.com>
Signed-off-by: KowalczykBartek <bkowalczyyk@gmail.com>

Fixes #15873
Motivation
Try to resolve child's output when steps is actually skipped.