argoproj/argo-workflows · error

unable to resolve outputs from scope: %w

Error message

unable to resolve outputs from scope: %w

What it means

After resolving output parameters, the controller resolves each of the template's output artifacts from the scope (workflow/controller/operator.go:3497). If artifact resolution fails for reasons other than the handled 'not found + optional' case, the error is wrapped with 'unable to resolve outputs from scope: %w' and template execution fails. The wrapped inner error (e.g. 'Unable to resolve: {{...}} expression' or an invalid artifact path) is the real cause.

Source

Thrown at workflow/controller/operator.go:3497

				// optional: fail terminally, mirroring simple-tag substitution semantics.
				return nil, argoerrors.Errorf(argoerrors.CodeBadRequest, "output parameter %q: %q is an absent optional (skipped/omitted node output with no default)", param.Name, param.ValueFrom.Parameter)
			}
			param.Value = wfv1.AnyStringPtr(val)
			param.ValueFrom = nil
			outputs.Parameters = append(outputs.Parameters, param)
		}
	}
	if len(tmpl.Outputs.Artifacts) > 0 {
		outputs.Artifacts = make([]wfv1.Artifact, 0)
		for _, art := range tmpl.Outputs.Artifacts {
			resolvedArt, err := scope.resolveArtifact(ctx, &art)
			if err != nil {
				// If the artifact was not found and is optional, don't mark an error
				if strings.Contains(err.Error(), "Unable to resolve") && art.Optional {
					woc.log.WithField("artifactName", art.Name).Warn(ctx, "Optional artifact was not found; it won't be available as an output")
					continue
				}
				return nil, fmt.Errorf("unable to resolve outputs from scope: %w", err)
			}
			if resolvedArt == nil {
				continue
			}
			resolvedArt.Name = art.Name
			outputs.Artifacts = append(outputs.Artifacts, *resolvedArt)
		}
	}
	return &outputs, nil
}

func generateOutputResultRegex(name string, parentTmpl *wfv1.Template) (string, string) {
	referenceRegex := fmt.Sprintf(`\.%s\.outputs\.result`, name)
	expressionRegex := fmt.Sprintf(`\[['\"]%s['\"]\]\.outputs.result`, name)
	if parentTmpl.DAG != nil {
		referenceRegex = "tasks" + referenceRegex
		expressionRegex = "tasks" + expressionRegex
	} else if parentTmpl.Steps != nil {

View on GitHub (pinned to 35bff19146)

Solutions

  1. Read the wrapped cause after '%w' — fix the referenced artifact/step name so it exists in scope (names are case-sensitive)
  2. Mark the artifact `optional: true` only when a missing artifact is acceptable — note the lenient path requires the error to contain 'Unable to resolve'
  3. Verify the upstream step actually succeeded and produced the artifact (check node status and pod logs)
  4. For subPath outputs, confirm the subdirectory exists at runtime or add a fallback in the container script
  5. Use `argo lint` and check DAG/task naming to catch broken references before submission

Example fix

# before
outputs:
  artifacts:
  - name: model
    from: '{{steps.train.outputs.artifacts.modle}}'   # typo
# after
outputs:
  artifacts:
  - name: model
    from: '{{steps.train.outputs.artifacts.model}}'
Defensive patterns

Strategy: validation

Validate before calling

# verify every artifact reference resolves to an existing step/task name
# argo lint workflow.yaml
# and mark artifacts that may legitimately be absent:
# outputs:
#   artifacts:
#   - name: report
#     optional: true
#     from: '{{steps.gen.outputs.artifacts.report}}'

Type guard

func artifactRefExists(ref string, scope map[string]any) bool {
    _, ok := scope[strings.Trim(ref, "{}")]
    return ok
}

Try / catch

null

Prevention

When it happens

Trigger: An output artifact references something absent from the scope: `from:` pointing at a step/task/artifact name that doesn't exist or is misspelled; `subPath:` on an artifact whose subdirectory doesn't exist; a `fromExpression:` that evaluates to nothing; non-optional artifact whose source didn't produce the file.

Common situations: Typos in `from: '{{steps.x.outputs.artifacts.y}}'` references; a task failed/skipped earlier so its artifact never existed; marking an artifact optional doesn't help when the error is something other than 'Unable to resolve' (e.g. malformed expression); container didn't write the declared output path.

Related errors


AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03). Data as JSON: /api/errors/c562c4215d4d1d23. Report an issue: GitHub.