{"record":{"id":"745b45d369e38be4","repo":"coleam00/Archon","slug":"composed-fan-out-failed-node-node-id","errorCode":null,"errorMessage":"❌ **Composed fan-out failed** (node `${node.id}`): ${currentItems.error}","messagePattern":"❌ \\*\\*Composed fan-out failed\\*\\* \\(node `(.+?)`\\): (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/workflows/src/dag-executor.ts","lineNumber":8962,"sourceCode":"      const itemsResolved = substituteNodeOutputRefs(itemsVarsResolved, ctx.nodeOutputs);\n      const parsed: unknown = JSON.parse(itemsResolved);\n      if (!Array.isArray(parsed)) {\n        return {\n          error:\n            `fan_out.items on '${node.id}' resolved to ${typeof parsed}, not a JSON array. ` +\n            `'${fanOut.items}' must reference a node output that produces a JSON array.`,\n        };\n      }\n      return { items: parsed };\n    } catch (err) {\n      return {\n        error: `fan_out.items on '${node.id}' could not be resolved to a JSON array: ${(err as Error).message}`,\n      };\n    }\n  };\n\n  const currentItems = resolveCurrentItems();\n  if ('error' in currentItems && persistedSnapshots === undefined) {\n    await notify(`❌ **Composed fan-out failed** (node \\`${node.id}\\`): ${currentItems.error}`);\n    return failResult(currentItems.error);\n  }\n  if ('error' in currentItems) {\n    getLog().warn(\n      { parentRunId: parentRun.id, nodeId: node.id, error: currentItems.error },\n      'workflow.compose_fan_out_item_drift_unreadable'\n    );\n  }\n\n  // Preflight BEFORE any instance spend: the target must still resolve (it may have been\n  // renamed or deleted since load), and its complete closure must remain suspension-free.\n  const resolved = await resolveFanOutChildDefinition(\n    deps,\n    cwd,\n    node.include,\n    ctx.workflowSourceRoots\n  );","sourceCodeStart":8944,"sourceCodeEnd":8980,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/workflows/src/dag-executor.ts#L8944-L8980","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# workflow yaml\n# before\nfan_out:\n  items: \"${steps.parse.output}\"        # parse node returned an object\n# after\nfan_out:\n  items: \"${steps.parse.output.records}\"  # reference the array field","handlingStrategy":"type-guard","validationCode":"// Verify the fan-out source is an array before the node runs:\nconst items = JSON.parse(upstreamOutput);\nif (!Array.isArray(items)) throw new Error('fan_out.items source must be a JSON array, got ' + typeof items);","typeGuard":"function isJsonArray(v: unknown): v is unknown[] {\n  return Array.isArray(v);\n}\n// use: if (isJsonArray(resolvedItems)) { /* safe to fan out */ }","tryCatchPattern":"try {\n  await runFanOut(node);\n} catch (e) {\n  const m = String(e.message ?? e).match(/Composed fan-out failed.*: (.+)/s);\n  if (m) console.error('fix fan_out.items resolution:', m[1]);\n  else throw e;\n}","preventionTips":["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"],"tags":["workflow","fan-out","json","dag-executor"],"backgroundTag":"fan-out-items-not-array","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}