coleam00/Archon · error
Node '${consumerId}' binding '${name}' reads '${directive.fr
Error message
Node '${consumerId}' binding '${name}' reads '${directive.from}', but node '${ref.nodeId}' did not run (skipped or pending), so it has no output to read. Declare 'if_skipped:' on the binding to supply a default for that branch, or guard '${consumerId}' with a 'when:' condition. What it means
A binding directive reads one upstream node's output, but the producer is absent, skipped, or pending (typical under trigger_rule: all_done across a skipped branch). If the directive declares if_skipped (presence-keyed, so null/false/0/'' count as real defaults), that value is used; otherwise the node fails explaining both remediation options. A failed producer is not covered here — it routes through assertProducerNotFailed and never falls back to if_skipped.
Source
Thrown at packages/workflows/src/dag-executor.ts:484
function resolveBindingDirective(
consumerId: string,
name: string,
directive: BindingDirective,
ctx: ShellInputContext
): JsonValue {
const ref = parseWholeOutputRef(directive.from);
if (ref === undefined) {
throw new Error(
`Node '${consumerId}' binding '${name}': 'from' must be exactly one whole ` +
`'$node.output' or '$node.output.field' reference, got '${directive.from}'.`
);
}
const producer = ctx.nodeOutputs.get(ref.nodeId);
if (producer === undefined || producer.state === 'skipped' || producer.state === 'pending') {
// Presence-keyed: `if_skipped: null` (or false/0/'') is a real declared default.
if (Object.hasOwn(directive, 'if_skipped')) return directive.if_skipped as JsonValue;
throw new Error(
`Node '${consumerId}' binding '${name}' reads '${directive.from}', but node ` +
`'${ref.nodeId}' did not run (skipped or pending), so it has no output to read. ` +
"Declare 'if_skipped:' on the binding to supply a default for that branch, or " +
`guard '${consumerId}' with a 'when:' condition.`
);
}
// A failed producer never falls back to `if_skipped` (#2696): that default exists for
// a branch that legitimately didn't run, not for a run that ran and produced a result
// that can't be trusted (a loop_group's failure paths carry the last completed
// iteration's real output text — non-empty, often valid JSON — which would otherwise
// resolve here as if the group had succeeded). Routes through `assertProducerNotFailed`
// in output-ref.ts (#2722, extending #2710's original guard to every whole-text reader
// of nodeOutputs through one shared function).
assertProducerNotFailed(
producer,
failed =>
`Node '${consumerId}' binding '${name}' reads '${directive.from}', but node ` +
`'${ref.nodeId}' failed (${failed.error}), so its output cannot be trusted. ` +View on GitHub (pinned to 0773b97458)
Solutions
- Add if_skipped: to the binding with a default (including if_skipped: null if that is the intended value).
- Add a when: condition on the consumer so it also skips when the producer skipped.
- Restructure dependencies so the producer always runs before the consumer (adjust trigger_rule or depends_on).
- If the producer actually failed, fix the producer — if_skipped does not apply to failures by design.
Example fix
// before
with:
coverage:
from: '$unit_tests.output.coverage' # unit_tests may be skipped
// after
with:
coverage:
from: '$unit_tests.output.coverage'
if_skipped: null Defensive patterns
Strategy: fallback
Validate before calling
function needsIfSkipped(directive: { from: string; if_skipped?: unknown }, skippable: Set<string>): string | null {
const producer = directive.from.replace(/^\$/, '').replace(/\.output.*$/, '');
return skippable.has(producer) && !('if_skipped' in directive)
? `Producer '${producer}' can be skipped; add if_skipped to binding reading '${directive.from}'`
: null;
} Try / catch
try {
runWorkflow(wf);
} catch (e) {
if (e instanceof Error && e.message.includes('did not run (skipped or pending)')) {
console.error('Upstream skipped and no if_skipped default declared:', e.message);
} else throw e;
} Prevention
- Whenever a producer has a when: condition, give every binding on its output an if_skipped default.
- Remember if_skipped is presence-keyed: if_skipped: null is a valid declared default.
- Remember failures do not consume if_skipped — handle producer failures separately.
- Prefer when: guards on consumers over all_done joins that read skipped outputs without defaults.
When it happens
Trigger: resolveBindingDirective with ctx.nodeOutputs lacking the producer or holding it with state 'skipped' or 'pending', and no if_skipped key on the directive — e.g. consumer joined on all_done while the producer's branch was skipped by its when: condition.
Common situations: Conditional deploy/test branches where one side skips but the join node always binds its output; fan-out or retry flows leaving a producer pending; forgetting if_skipped when tolerating skipped upstreams; misreading a failed node as skipped (failures do not take the default).
Related errors
- Binding value '$${wholeRef.nodeId}.output' references node '
- '${ref}' references node '${nodeId}', but no node with that
- Node '${consumerId}' binding '${name}': an object value must
- Node '${consumerId}' binding '${name}': 'from' must be exact
- producer-not-run
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/d123d5bc747f22ef.
Report an issue: GitHub.