{"record":{"id":"528ec6e79db7c05e","repo":"coleam00/Archon","slug":"node-consumerid-binding-name-from-mus","errorCode":null,"errorMessage":"Node '${consumerId}' binding '${name}': 'from' must be exactly one whole '$node.output' or '$node.output.field' reference, got '${directive.from}'.","messagePattern":"Node '(.+?)' binding '(.+?)': 'from' must be exactly one whole '\\$node\\.output' or '\\$node\\.output\\.field' reference, got '(.+?)'\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/workflows/src/dag-executor.ts","lineNumber":475,"sourceCode":"        `Node '${consumerId}' binding '${name}': an object value must be a binding directive ` +\n          \"{ from: '$node.output[.field]', if_skipped?: <value> }. Use a string, number, \" +\n          'boolean, null, or array for a literal value.'\n      );\n    }\n    resolved[name] = resolveWorkflowValue(rawValue, ctx, runInputs, true);\n  }\n  return resolved;\n}\n\nfunction resolveBindingDirective(\n  consumerId: string,\n  name: string,\n  directive: BindingDirective,\n  ctx: ShellInputContext\n): JsonValue {\n  const ref = parseWholeOutputRef(directive.from);\n  if (ref === undefined) {\n    throw new Error(\n      `Node '${consumerId}' binding '${name}': 'from' must be exactly one whole ` +\n        `'$node.output' or '$node.output.field' reference, got '${directive.from}'.`\n    );\n  }\n  const producer = ctx.nodeOutputs.get(ref.nodeId);\n  if (producer === undefined || producer.state === 'skipped' || producer.state === 'pending') {\n    // Presence-keyed: `if_skipped: null` (or false/0/'') is a real declared default.\n    if (Object.hasOwn(directive, 'if_skipped')) return directive.if_skipped as JsonValue;\n    throw new Error(\n      `Node '${consumerId}' binding '${name}' reads '${directive.from}', but node ` +\n        `'${ref.nodeId}' did not run (skipped or pending), so it has no output to read. ` +\n        \"Declare 'if_skipped:' on the binding to supply a default for that branch, or \" +\n        `guard '${consumerId}' with a 'when:' condition.`\n    );\n  }\n  // A failed producer never falls back to `if_skipped` (#2696): that default exists for\n  // a branch that legitimately didn't run, not for a run that ran and produced a result\n  // that can't be trusted (a loop_group's failure paths carry the last completed","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/workflows/src/dag-executor.ts#L457-L493","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set from: to exactly one '$node.output' or '$node.output.field'.","Move literal text/interpolation into the node body (bash:/script:/prompt:) or a downstream template instead of the binding.","For run inputs, note $INPUTS refs are resolved by resolveWorkflowValue, not by binding directives — use a plain string value for those."],"exampleFix":"// before\nwith:\n  msg:\n    from: 'Result: $step1.output'\n// after\nwith:\n  msg:\n    from: '$step1.output'   # literal text goes in the command/prompt body","handlingStrategy":"validation","validationCode":"const WHOLE_REF = /^\\$[A-Za-z0-9_-]+\\.output(\\.[A-Za-z0-9_-]+)?$/;\nfunction validateFrom(from: string): string | null {\n  return WHOLE_REF.test(from) ? null : `'from' must be '$node.output' or '$node.output.field', got '${from}'`;\n}","typeGuard":null,"tryCatchPattern":"try {\n  runWorkflow(wf);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"'from' must be exactly one whole\")) {\n    console.error('Malformed binding directive:', e.message);\n  } else throw e;\n}","preventionTips":["Keep from: to a single pure ref — no prose, no concatenation, no multiple refs.","Move any literal text or interpolation into the node body, not the binding.","Type the directive in code as { from: `$${string}.output${string}` } where possible."],"tags":["workflow","binding","validation","syntax"],"backgroundTag":"invalid-binding-directive","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}