coleam00/Archon · error
❌ **Composed fan-out failed** (node `${node.id}`): ${current
Error message
❌ **Composed fan-out failed** (node `${node.id}`): ${currentItems.error} What it means
In the composed fan-out path (packages/workflows/src/dag-executor.ts:8962), resolveCurrentItems must resolve the node's fan_out.items into a JSON array. If resolution fails and no persisted snapshots exist (fresh execution), the executor notifies '❌ Composed fan-out failed' with the resolution error and fails the node; when snapshots exist it warns and falls back to them instead.
Source
Thrown at packages/workflows/src/dag-executor.ts:8962
const itemsResolved = substituteNodeOutputRefs(itemsVarsResolved, ctx.nodeOutputs);
const parsed: unknown = JSON.parse(itemsResolved);
if (!Array.isArray(parsed)) {
return {
error:
`fan_out.items on '${node.id}' resolved to ${typeof parsed}, not a JSON array. ` +
`'${fanOut.items}' must reference a node output that produces a JSON array.`,
};
}
return { items: parsed };
} catch (err) {
return {
error: `fan_out.items on '${node.id}' could not be resolved to a JSON array: ${(err as Error).message}`,
};
}
};
const currentItems = resolveCurrentItems();
if ('error' in currentItems && persistedSnapshots === undefined) {
await notify(`❌ **Composed fan-out failed** (node \`${node.id}\`): ${currentItems.error}`);
return failResult(currentItems.error);
}
if ('error' in currentItems) {
getLog().warn(
{ parentRunId: parentRun.id, nodeId: node.id, error: currentItems.error },
'workflow.compose_fan_out_item_drift_unreadable'
);
}
// Preflight BEFORE any instance spend: the target must still resolve (it may have been
// renamed or deleted since load), and its complete closure must remain suspension-free.
const resolved = await resolveFanOutChildDefinition(
deps,
cwd,
node.include,
ctx.workflowSourceRoots
);View on GitHub (pinned to 0773b97458)
Solutions
- Read currentItems.error — it names the node and why items could not be resolved to a JSON array
- Fix the upstream node so fan_out.items resolves to a JSON array (use structured output, not prose)
- Correct the fan_out.items expression to reference the right output field
- Re-run with persisted snapshots if a prior successful snapshot exists, so the warn-and-fallback path applies
Example fix
# workflow yaml
# before
fan_out:
items: "${steps.parse.output}" # parse node returned an object
# after
fan_out:
items: "${steps.parse.output.records}" # reference the array field Defensive patterns
Strategy: type-guard
Validate before calling
// Verify the fan-out source is an array before the node runs:
const items = JSON.parse(upstreamOutput);
if (!Array.isArray(items)) throw new Error('fan_out.items source must be a JSON array, got ' + typeof items); Type guard
function isJsonArray(v: unknown): v is unknown[] {
return Array.isArray(v);
}
// use: if (isJsonArray(resolvedItems)) { /* safe to fan out */ } Try / catch
try {
await runFanOut(node);
} catch (e) {
const m = String(e.message ?? e).match(/Composed fan-out failed.*: (.+)/s);
if (m) console.error('fix fan_out.items resolution:', m[1]);
else throw e;
} Prevention
- Ensure upstream nodes emit arrays via structured output, not prose-wrapped JSON
- Point fan_out.items at the exact array field of the upstream output
- Test fan-out nodes against representative upstream outputs
- Prefer reusing persisted snapshots (resume path) over re-resolving changed outputs
When it happens
Trigger: The fan_out.items expression on a node resolves to something that cannot be parsed as a JSON array — the expression yields a non-array value, an object, undefined, or malformed JSON — during a composed (e.g. resumed/child) fan-out execution without persisted item snapshots.
Common situations: An upstream node outputting an object where an array was expected; a fan_out.items expression with a typo so it resolves to undefined; JSON output from a prompt node wrapped in prose so parsing fails; rerunning a composed node whose parent's item shape changed.
Related errors
- inputs must be a JSON-encoded object of string values
- ${label} must be a JSON-encoded object
- config must be a JSON-encoded object
- Loop-group node '${node.id}' failed at iteration ${String(i)
- Loop-group node '${node.id}' exceeded max iterations (${Stri
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/745b45d369e38be4.
Report an issue: GitHub.