{"record":{"id":"b45042e9905c8398","repo":"coleam00/Archon","slug":"loop-group-node-node-id-exceeded-max-iteratio","errorCode":null,"errorMessage":"Loop-group node '${node.id}' exceeded max iterations (${String(group.max_iterations)}) ${describeUnmetCompletion(group)}","messagePattern":"Loop-group node '(.+?)' exceeded max iterations \\((.+?)\\) (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/workflows/src/dag-executor.ts","lineNumber":5404,"sourceCode":"        // finalizeLoopFromSignal call above). Only the plain `loop` gate carries it.\n      });\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-group node '${node.id}' exceeded max iterations (${String(group.max_iterations)}) ${describeUnmetCompletion(group)}`;\n  getLog().warn(\n    { nodeId: node.id, maxIterations: group.max_iterations, signal: group.until },\n    'loop_group_node.max_iterations_reached'\n  );\n  await safeSendMessage(platform, conversationId, errorMsg, msgContext);\n  return {\n    state: 'failed',\n    output: lastIterationOutput,\n    error: errorMsg,\n    costUsd: loopTotalCostUsd,\n    ...(loopTotalTokens !== undefined ? { tokens: loopTotalTokens } : {}),\n    loopIterations: group.max_iterations,\n  };\n}\n\n/**\n * Clone a body node with `$LOOP_PREV.<id>.output[.field]` refs and `$LOOP_USER_INPUT`\n * pre-substituted into every text field a body executor reads prompts from. Used by\n * {@link executeLoopGroupNode} so the sealed body sub-DAG's executors stay unaware of the\n * enclosing loop iteration (the body's own executors call substituteWorkflowVariables, but\n * that uses the run's user_message — not the loop's per-iteration user input — so\n * $LOOP_USER_INPUT must be resolved here, at the loop-group level).\n *","sourceCodeStart":5386,"sourceCodeEnd":5422,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/workflows/src/dag-executor.ts#L5386-L5422","documentation":"A loop-group node (packages/workflows/src/dag-executor.ts:5404) ran up to group.max_iterations without its until/completion condition being satisfied. The executor logs 'loop_group_node.max_iterations_reached', notifies the conversation, and returns the node failed with the last iteration's output; describeUnmetCompletion appends why the completion signal was unmet.","triggerScenarios":"A loop-group whose until condition (e.g. all-done signal, convergence check) never becomes true within max_iterations — completion detection depends on structured output the model/script never emits, or the task genuinely needs more iterations than allowed.","commonSituations":"Prompt nodes never emitting the expected completion field so the check never passes; max_iterations set too low for the batch size; a condition written against the wrong output field; AI responses drifting from the expected schema.","solutions":["Read describeUnmetCompletion in the message to see why the condition was never met","Raise max_iterations on the loop-group node if the work legitimately needs more passes","Make sure body nodes emit the exact structured field the until condition checks (use structured output)","Add per-iteration failure handling so a stuck iteration fails fast instead of burning all iterations"],"exampleFix":"# workflow yaml\n# before\nmax_iterations: 3\nuntil: \"${loop.all_passed}\"\n# after\nmax_iterations: 10\nuntil: \"${loop.all_passed}\"  # and ensure body node outputs include all_passed","handlingStrategy":"validation","validationCode":"// Pre-check that the completion signal's field is actually produced by the body:\nconst out = await runBodyNodeOnce(group);\nif (!(completionField in out)) {\n  throw new Error(`body output missing completion field '${completionField}' — until will never be satisfied`);\n}","typeGuard":"function hasCompletionField(o: unknown, field: string): o is Record<string, unknown> {\n  return typeof o === 'object' && o !== null && field in o;\n}","tryCatchPattern":"try {\n  const r = await runLoopGroup(group);\n  if (r.state === 'failed' && r.error?.includes('exceeded max iterations')) {\n    console.error(r.error); // includes describeUnmetCompletion — read why the signal was unmet\n  }\n} catch (e) { /* escalate: loops should not be retried blindly */ }","preventionTips":["Make body nodes emit the exact structured field the until condition checks","Budget max_iterations generously relative to the work per iteration","Keep the until expression in sync with the body node's output schema","Add early-exit criteria so stuck loops fail with a clearer message"],"tags":["workflow","loop","max-iterations","timeout","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"}