coleam00/Archon · error
workflow.compose_fan_out_suspension_rejected
workflow.compose_fan_out_suspension_rejected
Error message
composed fan-out node '${node.id}': the composed block '${node.include}' contains suspension-capable path${bodySuspensions.length === 1 ? '' : 's'} ${names}. An in-parent fan-out has no per-instance pause cursor, so it cannot safely resume a partly completed body. Remove the suspension path, or invoke the block once through 'include:' or 'workflow:'. What it means
A composed fan-out node (`include:` block expanded per item in the parent run) is refused because the composed body contains a suspension-capable path (e.g. an approval gate). In-parent fan-out instances share the parent's execution and have no per-instance pause cursor, so a partly completed body could not be resumed safely.
Source
Thrown at packages/workflows/src/dag-executor.ts:8996
node.include,
ctx.workflowSourceRoots
);
if ('unresolved' in resolved) {
const msg =
`composed fan-out node '${node.id}': cannot resolve the composed block '${node.include}' — ` +
`${resolved.unresolved}. Fix the include target name.`;
await notify(`❌ **Composed fan-out blocked** (node \`${node.id}\`): ${msg}`);
return failResult(msg);
}
const bodySuspensions = collectComposedSuspensionPaths(resolved.definition, resolved.definitions);
if (bodySuspensions.length > 0) {
const names = bodySuspensions.map(entry => `'${entry.id}' (${entry.reason})`).join(', ');
const msg =
`composed fan-out node '${node.id}': the composed block '${node.include}' contains ` +
`suspension-capable path${bodySuspensions.length === 1 ? '' : 's'} ${names}. An in-parent ` +
'fan-out has no per-instance pause cursor, so it cannot safely resume a partly completed ' +
"body. Remove the suspension path, or invoke the block once through 'include:' or 'workflow:'.";
getLog().warn(
{ parentRunId: parentRun.id, nodeId: node.id, include: node.include },
'workflow.compose_fan_out_suspension_rejected'
);
await notify(`❌ **Composed fan-out blocked** (node \`${node.id}\`): ${msg}`);
return failResult(msg);
}
let computedSnapshots: ReturnType<typeof buildInstanceSnapshots>;
if (persistedSnapshots === undefined) {
// Freeze every resolved binding together with the item before any instance starts.
// A resumed run must never combine completed work from the old bindings with a
// retried instance resolved from changed upstream output.
let staticInputs: Record<string, JsonValue> = {};
const parentInputs = resolveRunInputs(parentRun);
try {
if (node.with !== undefined) {
const resolutionCtx: ShellInputContext = {
workflowRun: parentRun,View on GitHub (pinned to 0773b97458)
Solutions
- Remove the suspension-capable path from the composed block
- Invoke the block once (not per-instance) via plain `include:` or `workflow:` so the engine's pause cursor applies
- Split the block: non-suspending part fans out, suspending part runs once
Example fix
# before
- id: loop
include: gated-batch # contains an approval node
fan_out: { over: items }
# after
- id: loop
include: plain-batch # suspension path removed
fan_out: { over: items } Defensive patterns
Strategy: validation
Validate before calling
// Ensure the composed block has no suspension-capable nodes before fan-out
const body = await composer.expand(node.include);
const suspensions = body.filter(n => n.type === 'approval' || n.canSuspend);
if (suspensions.length > 0) throw new Error(`${node.include} cannot fan out: contains ${suspensions.map(s => s.id).join(', ')}`); Type guard
function isFanOutSafeBlock(body: { id: string; canSuspend?: boolean }[]): boolean {
return !body.some(n => n.canSuspend === true);
} Prevention
- Keep approval/human-gate nodes out of blocks intended for fan-out reuse
- Review blocks that gain suspension paths for existing fan-out consumers
- Run suspension-capable blocks once via include:/workflow: instead of per-instance
When it happens
Trigger: Executing a composed fan-out node whose `node.include` block resolves to a body containing suspension-capable nodes; detected before instances run.
Common situations: Reusing a block that includes a human-approval step inside a per-item fan-out; a block gained a suspension path after the fan-out was authored.
Related errors
- workflow.compose_fan_out_shared_checkout_collision
- ❌ **Composed fan-out failed** (node `${node.id}`): ${current
- workflow.fan_out_interactive_target
- workflow.fan_out_shared_checkout_collision
- workflow.subrun_schema_uncompilable
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/8183f5af4db31f70.
Report an issue: GitHub.