Skip to main content
Workflow errors use the standard error envelope: a type, a code and a human-readable message. Refusals fall into three groups, and knowing which one you are looking at tells you where to fix it:
  • Shape — the definition failed the schema. Always invalid_pipeline_definition; the message names the JSON path and the rule.
  • Graph — the definition parsed but its steps or references do not hang together. Each has its own code.
  • Run time — the definition is fine but something failed while it executed.
Shape refusals are raised wherever a definition is written or sent inline — creating, updating, and running or estimating an inline definition. Graph refusals are raised there and again on every run, including a run of a stored workflow. A definition you cannot save is a definition you cannot run.

Shape refusals

All of these come back as code: "invalid_pipeline_definition", with the detail in the message. A binding in a numeric or enum field lands here too: the value is validated with its tokens masked to a placeholder string, and no string satisfies a number or an enum.

Graph refusals

forward_step_ref and binding_out_of_scope exist because the alternative is worse than an error. A reference the resolver cannot reach becomes undefined at run time with no error raised — and the step is dispatched and billed with a missing input. That is why a fallback such as ${steps.late.output.text | "n/a"} does not excuse an out-of-scope first alternative: every alternative of a token is checked.

Run-time failures

These appear as the failing step’s error in stepRuns, and as the run’s own error when failure_mode is fail_fast.

Retries

A step failure is retried only if a retry block asks for it and the code is on the allowed list. See Step types for the default list and the codes that are never retried. foreach_empty_body, foreach_unknown_step_type, parallel_empty_branch and parallel_unknown_step_type are not on the never-retry list, but nothing about them changes between attempts — listing one in retryable_errors buys retries that cannot succeed.

Why so many refusals

Every code on this page replaces a silent failure. A binding that resolves to nothing raises nothing: the step still runs, still calls the provider, and is still billed — it just receives a blank where you meant a value. Refusing the definition at write time is the only place that mistake is free.