coleam00/Archon · error

Loop node ' ' exceeded max iterations ( )

Error message

Loop node '${node.id}' exceeded max iterations (${String(loop.max_iterations)}) ${describeUnmetCompletion(loop)}

What it means

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.

Solutions

  1. Check the message's describeUnmetCompletion suffix for why the loop signal was never met
  2. Increase max_iterations on the loop node
  3. Verify the body node's structured output actually contains the field referenced by until/while
  4. Add exit criteria or a break condition inside the body so pathological runs terminate early with a clear error

Example fix

# workflow yaml
# before
max_iterations: 5
until: "${steps.review.approved == true}"
# after
max_iterations: 15
until: "${steps.review.approved == true}"  # ensure review node outputs approved as boolean
Defensive patterns

Strategy: validation

Validate before calling

// Prove the loop signal can be satisfied before looping:
const probe = await runBodyOnce(loop);
if (typeof probe[signalField] !== 'boolean') {
  throw new Error(`loop signal field '${signalField}' missing or not boolean in body output`);
}

Type guard

function isLoopResult(r: { state: string; error?: string; data?: { maxIterations?: number } }): r is { state: 'failed'; error: string; data: { maxIterations: number } } {
  return r.state === 'failed' && r.error?.includes('exceeded max iterations') === true;
}

Try / catch

try {
  const r = await runLoopNode(loop);
  if (r.state === 'failed' && r.error?.startsWith('Loop node')) {
    console.error(`${r.error} (ran ${r.data.maxIterations} iterations, output=${JSON.stringify(r.output)})`);
  }
} catch (e) { /* escalate; do not auto-retry an exhausted loop */ }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01). Data as JSON: /api/errors/c01a66ea0b4028b1. Report an issue: GitHub.

Appendix: source

Thrown at packages/workflows/src/dag-executor.ts:7000

      // in multi-node workflows. Resume correctness relies on the 'paused' DB status, not
      // on the node's output state.
      return {
        state: 'completed',
        output: lastIterationOutput,
        costUsd: loopTotalCostUsd,
        ...(loopTotalTokens !== undefined ? { tokens: loopTotalTokens } : {}),
        loopIterations: i,
      };
    }
  }

  // Max iterations exceeded
  const errorMsg = `Loop node '${node.id}' exceeded max iterations (${String(loop.max_iterations)}) ${describeUnmetCompletion(loop)}`;
  getLog().warn(
    { nodeId: node.id, maxIterations: loop.max_iterations, signal: loop.until },
    'loop_node.max_iterations_reached'
  );
  await safeSendMessage(platform, conversationId, errorMsg, msgContext);
  return failLoopNode(errorMsg, {
    output: lastIterationOutput,
    costUsd: loopTotalCostUsd,
    ...(loopTotalTokens !== undefined ? { tokens: loopTotalTokens } : {}),
    loopIterations: loop.max_iterations,
    data: { maxIterations: loop.max_iterations },
  });
}

/**
 * Pause the run for a human/system gate — the single persist path for all five
 * suspend sites (`loop_group`, `loop`, `approval`, `workflow:` child, and the
 * container write-back gate). By default, tolerates a lost CAS when the run
 * was externally transitioned while the gate was being raised — e.g. a killed
 * CLI's signal cleanup marked the run failed mid-pause (#1123), or an operator
 * cancelled it from another surface. `pauseWorkflowRun`'s UPDATE only matches
 * status='running'; when it misses, re-read the status: any non-running status
 * means the pause lost a legitimate external race — log, skip the

View on GitHub (pinned to 0773b97458)