coleam00/Archon · warning
loop_group_nested_pause_not_escalated
loop_group_nested_pause_not_escalated
Error message
Node '${id}': this loop_group's terminal sink ('${sink.id}') is itself an interactive loop/loop_group — a pause inside it does not escalate to stop this loop_group's own iteration (#2753). The outer loop can run further iterations while a human's answer to the inner pause is still pending. Only a gate node directly as the terminal sink correctly stops the enclosing loop_group (#2707 step 3). What it means
This loader warning fires when a loop_group's sole terminal sink is not a gate but another interactive loop or loop_group node. A pause (human gate) inside that nested interactive sink does not escalate to stop the outer loop_group's iteration (#2753), so the outer loop can start another iteration while the human's answer to the inner pause is still pending. Only a gate node placed directly as the terminal sink correctly pauses the enclosing loop_group (#2707 step 3).
Source
Thrown at packages/workflows/src/loader.ts:583
}
}
// A body-terminal sink that is itself an interactive loop/loop_group can pause
// without stopping THIS group (#2753) — a bare gate sink is excluded here since
// that case is already correctly handled above (and by #2707 step 3 at runtime);
// this covers a sink whose own pause is trapped one level down instead.
if (bodySinks.length === 1) {
const sink = bodySinks[0];
if (!isIncludeDirective(sink) && !isGateNode(sink) && isUnescalatableInteractiveSink(sink)) {
const message =
`Node '${id}': this loop_group's terminal sink ('${sink.id}') is itself an ` +
'interactive loop/loop_group — a pause inside it does not escalate to stop ' +
"this loop_group's own iteration (#2753). The outer loop can run further iterations " +
"while a human's answer to the inner pause is still pending. Only a gate node " +
'directly as the terminal sink correctly stops the enclosing loop_group ' +
'(#2707 step 3).';
warnings.push(message);
getLog().warn(
{ id, sinkId: sink.id, warning: message },
'loop_group_nested_pause_not_escalated'
);
}
}
}
// Recurse into a loop_group body — mirrors collectUnknownNodeKeys's own body
// recursion. Resolved and raw body arrays share index order (Zod arrays
// preserve it), so they're zipped by position rather than by id.
if (isLoopGroupNode(node) && raw !== null && typeof raw === 'object') {
const rawGroup = (raw as Record<string, unknown>).loop_group;
const rawBody =
rawGroup !== null && typeof rawGroup === 'object'
? (rawGroup as Record<string, unknown>).nodes
: undefined;
if (Array.isArray(rawBody)) {
// `id` is unused on the IncludeDirective early-return path above, so aView on GitHub (pinned to 0773b97458)
Solutions
- Hoist a gate node out of the nested loop and place it directly as the outer loop_group's terminal sink
- Flatten the nesting: move the inner loop's nodes into the outer body with a gate as the final node
- Restructure so the inner interactive loop lives outside the outer loop_group, or accept that outer iterations continue while the inner pause is pending
- If the inner group must stay, add a gate after it in the outer body as the terminal sink
Example fix
# before
outer:
loop_group:
nodes:
- id: step
agent: ...
- id: inner # interactive loop_group as sole sink
loop_group:
nodes:
- id: review
gate: {}
# after
outer:
loop_group:
nodes:
- id: step
agent: ...
- id: review
gate: {} # gate directly as the outer terminal sink Defensive patterns
Strategy: validation
Validate before calling
function sinkIsGate(group) {
const dependedOn = new Set(group.nodes.flatMap(n => n.depends_on ?? []));
const sinks = group.nodes.filter(n => !dependedOn.has(n.id));
return sinks.length === 1 && 'gate' in sinks[0];
} Type guard
function isGateNode(n) { return typeof n === 'object' && n !== null && 'gate' in n; } Prevention
- Place a gate node directly as the loop_group's terminal sink rather than wrapping it in a nested loop/loop_group
- Avoid nesting interactive loop/loop_group nodes as the last node of a loop body
- Review the loader warnings on every workflow load in CI
- Model human approval at the outermost loop level where the pause should apply
When it happens
Trigger: Loading a workflow YAML where a loop_group body's single un-depended-on node is itself a loop or loop_group node that contains (or may contain) a pause, and isUnescalatableInteractiveSink(sink) returns true. Bare gate sinks are excluded — they are handled by the dedicated gate warnings.
Common situations: Nesting an approval loop inside each iteration of an outer retry loop and expecting the inner gate to halt the outer iteration; wrapping a gate in a sub-loop 'for structure'; refactoring a flat gate-in-body design into a nested group without realizing pause escalation stops at the gate boundary.
Related errors
- loop_group_gate_not_terminal_sink
- loop_group_gate_completion_not_referenced
- node_loop_group_until_deprecated
- Cannot execute run '${detachedPreCreatedRun.id}': it belongs
- Dry-run failed; missing stubs: ${blockingMissingStubs.join('
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/f59f0d3888fc4005.
Report an issue: GitHub.