argoproj/argo-workflows · error
400
400
Error message
failed to evaluate expression %q
What it means
resolveParameter evaluates a parameter expression against the workflow scope. When the expression evaluates to nil — an unhandled absent optional, such as a skipped node's output parameter that has no default and was referenced without the `??` operator — the controller treats it as a resolution failure with code 400, mirroring the inline {{= ...}} semantics. If the caller declared valueFrom.default, that default is applied on this error path instead of failing the step.
Source
Thrown at workflow/controller/scope.go:185
func (s *wfScope) resolveParameter(p *wfv1.ValueFrom) (any, bool, error) {
if p == nil || (p.Parameter == "" && p.Expression == "") {
return "", false, nil
}
if p.Expression != "" {
env := env.GetFuncMap(s.scope.AsAnyMap())
program, err := expr.Compile(p.Expression, expr.Env(env))
if err != nil {
return nil, false, err
}
val, err := expr.Run(program, env)
if err != nil {
return nil, false, err
}
if val == nil {
// A nil result is an unhandled absent optional (e.g. a skipped node's defaultless output
// referenced without `??`). Mirror the inline {{= ...}} semantics and treat it as a
// resolution failure; the caller's error path applies valueFrom.default when declared.
return nil, false, errors.Errorf(errors.CodeBadRequest, "failed to evaluate expression %q", p.Expression)
}
return val, false, nil
}
tag, val, err := s.resolveVar(p.Parameter)
// IsSkipped is true only for a placeholder written via Key.SetSkipped (a skipped/omitted node
// output with no producer default), i.e. an absent optional.
return val, s.scope.IsSkipped(tag), err
}
func (s *wfScope) resolveArtifact(ctx context.Context, art *wfv1.Artifact) (*wfv1.Artifact, error) {
if art == nil || (art.From == "" && art.FromExpression == "") {
return nil, nil
}
var err error
var val any
if art.FromExpression != "" {View on GitHub (pinned to 35bff19146)
Solutions
- Add a default to the output parameter in the producing template (valueFrom.default)
- Use the null-coalescing operator in the expression, e.g. {{tasks.x.outputs.parameters.y ?? 'fallback'}}
- Guard the consuming step with `when` so it only runs when the producer executed
Example fix
// before
value: '{{tasks.build.outputs.parameters.version}}'
// after
value: '{{tasks.build.outputs.parameters.version ?? "unknown"}}' Defensive patterns
Strategy: validation
Validate before calling
// before submitting, ensure producers have defaults or consumers have fallbacks
for _, t := range wf.Spec.Templates {
for _, op := range t.Outputs.Parameters {
if op.ValueFrom != nil && op.ValueFrom.Default == nil {
// downstream refs must use `??`
}
}
} Type guard
func hasFallback(expr string) bool { return strings.Contains(expr, "??") } Try / catch
if resolved, ok, err := resolveParameter(p); err != nil || !ok {
if p.Value != nil { useDefault(p) } else { return fmt.Errorf("expression %q unresolved: %w", p.Expression, err) }
} Prevention
- Always set valueFrom.default on output parameters of templates that can be skipped
- Use `??` when referencing outputs of conditional tasks
- Guard dependent steps with `when` conditions
When it happens
Trigger: Referencing {{tasks.x.outputs.parameters.y}} (or similar) in an expression when task x was skipped and its output parameter has no default, without using `??` to supply a fallback.
Common situations: Conditional steps (when:), failed/skipped DAG branches whose downstream steps still reference their outputs; workflows refactored to add `when` guards without updating downstream parameter references.
Related errors
- failed to check CronWorkflow '%s' stopping condition: %w
- failed to read container args file %s: %w
- failed to unmarshal container args: %w
- failed to read template: %w
- failed to start command: %w
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/5ccc137465be3036.
Report an issue: GitHub.