coleam00/Archon · error

Node '${consumerId}' binding '${name}': 'from' must be exact

Error message

Node '${consumerId}' binding '${name}': 'from' must be exactly one whole '$node.output' or '$node.output.field' reference, got '${directive.from}'.

What it means

A binding directive's from field must parse as exactly one whole output reference — '$node.output' or '$node.output.field' — via parseWholeOutputRef. Anything else (interpolated text, multiple refs, wrong syntax like $node.result or node.output without $) fails here. The directive form deliberately does not support template splicing; use a plain string value for that.

Source

Thrown at packages/workflows/src/dag-executor.ts:475

        `Node '${consumerId}' binding '${name}': an object value must be a binding directive ` +
          "{ from: '$node.output[.field]', if_skipped?: <value> }. Use a string, number, " +
          'boolean, null, or array for a literal value.'
      );
    }
    resolved[name] = resolveWorkflowValue(rawValue, ctx, runInputs, true);
  }
  return resolved;
}

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

View on GitHub (pinned to 0773b97458)

Solutions

  1. Set from: to exactly one '$node.output' or '$node.output.field'.
  2. Move literal text/interpolation into the node body (bash:/script:/prompt:) or a downstream template instead of the binding.
  3. For run inputs, note $INPUTS refs are resolved by resolveWorkflowValue, not by binding directives — use a plain string value for those.

Example fix

// before
with:
  msg:
    from: 'Result: $step1.output'
// after
with:
  msg:
    from: '$step1.output'   # literal text goes in the command/prompt body
Defensive patterns

Strategy: validation

Validate before calling

const WHOLE_REF = /^\$[A-Za-z0-9_-]+\.output(\.[A-Za-z0-9_-]+)?$/;
function validateFrom(from: string): string | null {
  return WHOLE_REF.test(from) ? null : `'from' must be '$node.output' or '$node.output.field', got '${from}'`;
}

Try / catch

try {
  runWorkflow(wf);
} catch (e) {
  if (e instanceof Error && e.message.includes("'from' must be exactly one whole")) {
    console.error('Malformed binding directive:', e.message);
  } else throw e;
}

Prevention

When it happens

Trigger: resolveBindingDirective called with directive.from that parseWholeOutputRef rejects: e.g. 'prefix $a.output suffix', '$a.output.deeply.nested.path' if unsupported, '$INPUTS.x', 'a.output' missing '$', or an empty string.

Common situations: Trying to concatenate a node output with literal text inside from:; mixing two refs in one from:; authoring the directive programmatically with a template string; copying prompt-style syntax ({{node.output}}) into the directive.

Related errors


AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01). Data as JSON: /api/errors/528ec6e79db7c05e. Report an issue: GitHub.