coleam00/Archon · error
Node '${context.consumerId}' field '${context.field}' cannot
Error message
Node '${context.consumerId}' field '${context.field}' cannot resolve '${ref}': ${error.message} (from OutputRefError) What it means
This error is thrown by `substituteNodeOutputRefs` in the workflow DAG executor when a consumer node's text references a producer node's output field via `$node_id.output.field`, but the field cannot be resolved. With `requiredContext` set (the `until_bash` completion-decision path), the executor converts the underlying `OutputRefError` into this wrapped error naming the consuming node, the field, the raw ref, and the cause — because an empty substitution would silently poison a loop-termination decision. It enforces the engine's no-silent-drop posture: unresolvable output references fail the consuming node loudly instead of splicing in an empty string.
Source
Thrown at packages/workflows/src/dag-executor.ts:1495
'failed branch.'
);
return escapedForBash
? shellQuoteOrFile(nodeOutput.output, nodeId, undefined, artifactsDir)
: nodeOutput.output;
}
// No-silent-drop field access (resolveNodeOutputField): prefers the parsed
// structuredOutput payload, falls back to parsing `output`, and THROWS an
// OutputRefError for an unresolvable reference (field not in the producer's
// declared schema, or a schemaless node whose output isn't JSON / lacks the
// key). The throw propagates to the dag-executor's per-node catch → the
// consuming node fails visibly instead of receiving a poisoned ''. The only
// value that resolves to empty is an author-declared-optional field.
let resolution: ReturnType<typeof resolveNodeOutputField>;
try {
resolution = resolveNodeOutputField(nodeOutput, nodeId, field);
} catch (error) {
if (requiredContext && error instanceof OutputRefError) {
throw requiredOutputRefError(requiredContext, match, error.message);
}
throw error;
}
if (resolution.kind === 'empty') return escapedForBash ? "''" : '';
const value = resolution.value;
// numbers and booleans are shell-safe without quoting: JSON disallows
// NaN/Infinity so String(number) is digits/sign/'.', and String(boolean) is
// 'true'/'false' — no shell metacharacters.
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
// Everything else takes the one value→text rule (strings raw; arrays/objects/
// null as canonical JSON so downstream tools like jq get one JSON literal),
// with the bash-escaping decision staying here at the call site.
const text = canonicalValueText(value);
return escapedForBash ? shellQuoteOrFile(text, nodeId, field, artifactsDir) : text;
}
);
}
View on GitHub (pinned to 0773b97458)
Solutions
- Check the referenced node id in the error against your workflow YAML; fix the typo or ensure the node runs (and completes) before the consuming node/loop condition.
- Verify the field name exists in the producer node's declared structuredOutput schema; add it to the schema or read an actual declared field.
- If the producer is a prompt/AI node without a schema, add a structuredOutput schema so `$id.output.field` has a defined source, or switch the condition to parse the whole output deliberately in the bash body.
- For until_bash conditions, guard the check: test whether the output parses/contains the field in bash before dereferencing, or ensure the producer is guarded by a `when:` condition so the ref is only evaluated on paths where it ran.
- If the empty-value fallback is genuinely intended, move the ref out of the requiredContext surface (until_bash) into a prompt or script where `$id.output.field` strictness is not required.
Example fix
// before (until_bash condition referencing undeclared field)
until_bash: |
[ "$reviewer.output.approved" = "true" ]
// after (producer declares the field in structuredOutput)
output_schema:
type: object
properties:
approved: { type: boolean }
required: [approved]
until_bash: |
[ "$reviewer.output.approved" = "true" ] Defensive patterns
Strategy: validation
Validate before calling
// Before declaring the until/loop condition, verify every $id.output.field ref
// targets a node id present in the workflow and a field declared in its schema:
function validateOutputRefs(
body: string,
nodes: Map<string, { fields?: readonly string[] }>
): string[] {
const errors: string[] = [];
const re = /\$([a-zA-Z_][a-zA-Z0-9_-]*)\.output(?:\.([a-zA-Z_][a-zA-Z0-9_]*))?/g;
for (const m of body.matchAll(re)) {
const [, nodeId, field] = m;
const node = nodes.get(nodeId);
if (!node) errors.push(`unknown node '${nodeId}'`);
else if (field && node.fields && !node.fields.includes(field))
errors.push(`field '${field}' not declared on node '${nodeId}'`);
}
return errors;
} Type guard
function isResolvableOutputRef(
nodeId: string,
field: string | undefined,
nodeOutputs: Map<string, NodeOutput>
): boolean {
const out = nodeOutputs.get(nodeId);
if (!out) return false;
if (!field) return out.state === 'succeeded';
try { resolveNodeOutputField(out, nodeId, field); return true; }
catch { return false; }
} Try / catch
try {
substituteNodeOutputRefs(prompt, nodeOutputs, true, artifactsDir, ctx);
} catch (err) {
if (err instanceof Error && /cannot resolve '/.test(err.message)) {
// Surface which consumer/field/ref failed; fix YAML id or schema — do not retry.
log.error({ message: err.message }, 'output_ref_unresolvable');
}
throw err;
} Prevention
- Declare a structuredOutput schema on every producer node whose fields are read via $id.output.field.
- Copy node ids exactly; run workflow validation/lint before executing to catch typo'd refs early.
- In until_bash conditions, only dereference fields on nodes guaranteed to have run and succeeded on that path.
- When a producer can legitimately skip or fail, guard consumers with `when:` conditions excluding the failed branch.
- After renaming a node id or schema field, grep all workflow YAML for the old identifier.
When it happens
Trigger: A node's prompt/script contains `$producer.output.field` where: (1) the producer node id is unknown (typo, or node hasn't produced output on this execution path); (2) the field is not in the producer's declared structuredOutput schema; (3) the producer is schemaless and its output is not parseable JSON or lacks the key; (4) the ref appears in an `until_bash` condition (requiredContext) where field refs are always strict, even for optional-looking paths.
Common situations: Typoed node id in an until_bash loop condition (near-miss hints like 'Did you mean: ...' appear in the underlying detail); referencing a field the producer's structuredOutput schema doesn't declare; expecting free-text AI output to be JSON with a field when the model returned prose; renaming a node id or field in the YAML without updating dependent until/loop conditions.
Related errors
- ${label}: unknown key '${key}' will be ignored.${hint}
- node_with_ignored
- Cannot execute run '${detachedPreCreatedRun.id}': it belongs
- Dry-run failed; missing stubs: ${blockingMissingStubs.join('
- result.error
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/9d0707bfbccda803.
Report an issue: GitHub.