{"record":{"id":"c01a66ea0b4028b1","repo":"coleam00/Archon","slug":"loop-node-node-id-exceeded-max-iterations","errorCode":null,"errorMessage":"Loop node '${node.id}' exceeded max iterations (${String(loop.max_iterations)}) ${describeUnmetCompletion(loop)}","messagePattern":"Loop node '(.+?)' exceeded max iterations \\((.+?)\\) (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/workflows/src/dag-executor.ts","lineNumber":7000,"sourceCode":"      // in multi-node workflows. Resume correctness relies on the 'paused' DB status, not\n      // on the node's output state.\n      return {\n        state: 'completed',\n        output: lastIterationOutput,\n        costUsd: loopTotalCostUsd,\n        ...(loopTotalTokens !== undefined ? { tokens: loopTotalTokens } : {}),\n        loopIterations: i,\n      };\n    }\n  }\n\n  // Max iterations exceeded\n  const errorMsg = `Loop node '${node.id}' exceeded max iterations (${String(loop.max_iterations)}) ${describeUnmetCompletion(loop)}`;\n  getLog().warn(\n    { nodeId: node.id, maxIterations: loop.max_iterations, signal: loop.until },\n    'loop_node.max_iterations_reached'\n  );\n  await safeSendMessage(platform, conversationId, errorMsg, msgContext);\n  return failLoopNode(errorMsg, {\n    output: lastIterationOutput,\n    costUsd: loopTotalCostUsd,\n    ...(loopTotalTokens !== undefined ? { tokens: loopTotalTokens } : {}),\n    loopIterations: loop.max_iterations,\n    data: { maxIterations: loop.max_iterations },\n  });\n}\n\n/**\n * Pause the run for a human/system gate — the single persist path for all five\n * suspend sites (`loop_group`, `loop`, `approval`, `workflow:` child, and the\n * container write-back gate). By default, tolerates a lost CAS when the run\n * was externally transitioned while the gate was being raised — e.g. a killed\n * CLI's signal cleanup marked the run failed mid-pause (#1123), or an operator\n * cancelled it from another surface. `pauseWorkflowRun`'s UPDATE only matches\n * status='running'; when it misses, re-read the status: any non-running status\n * means the pause lost a legitimate external race — log, skip the","sourceCodeStart":6982,"sourceCodeEnd":7018,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/workflows/src/dag-executor.ts#L6982-L7018","documentation":"A loop (while/until) node (packages/workflows/src/dag-executor.ts:7000) exhausted loop.max_iterations without its until condition being satisfied. The executor logs 'loop_node.max_iterations_reached', notifies the conversation, and fails the node via failLoopNode with the last iteration output, iteration count, and maxIterations data.","triggerScenarios":"A loop node whose until/while condition — typically evaluated over a prompt node's structured output — never evaluates true within max_iterations, e.g. the model never emits the sentinel field the signal checks, or the task needs more passes than configured.","commonSituations":"Agent refine loops where the AI keeps saying 'not done' in the wrong field; max_iterations too low for the convergence needed; a signal expression referencing a renamed output key; retriable external calls failing every pass.","solutions":["Check the message's describeUnmetCompletion suffix for why the loop signal was never met","Increase max_iterations on the loop node","Verify the body node's structured output actually contains the field referenced by until/while","Add exit criteria or a break condition inside the body so pathological runs terminate early with a clear error"],"exampleFix":"# workflow yaml\n# before\nmax_iterations: 5\nuntil: \"${steps.review.approved == true}\"\n# after\nmax_iterations: 15\nuntil: \"${steps.review.approved == true}\"  # ensure review node outputs approved as boolean","handlingStrategy":"validation","validationCode":"// Prove the loop signal can be satisfied before looping:\nconst probe = await runBodyOnce(loop);\nif (typeof probe[signalField] !== 'boolean') {\n  throw new Error(`loop signal field '${signalField}' missing or not boolean in body output`);\n}","typeGuard":"function isLoopResult(r: { state: string; error?: string; data?: { maxIterations?: number } }): r is { state: 'failed'; error: string; data: { maxIterations: number } } {\n  return r.state === 'failed' && r.error?.includes('exceeded max iterations') === true;\n}","tryCatchPattern":"try {\n  const r = await runLoopNode(loop);\n  if (r.state === 'failed' && r.error?.startsWith('Loop node')) {\n    console.error(`${r.error} (ran ${r.data.maxIterations} iterations, output=${JSON.stringify(r.output)})`);\n  }\n} catch (e) { /* escalate; do not auto-retry an exhausted loop */ }","preventionTips":["Use structured output on the loop body so the until field is reliable","Set max_iterations above the realistic convergence count","Reference the correct output key in until/while expressions after any renames","Monitor loop iterations via run telemetry to catch near-limit runs"],"tags":["workflow","loop","max-iterations","dag-executor"],"backgroundTag":"loop-max-iterations-exceeded","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}