{"record":{"id":"f59f0d3888fc4005","repo":"coleam00/Archon","slug":"loop-group-nested-pause-not-escalated","errorCode":"loop_group_nested_pause_not_escalated","errorMessage":"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).","messagePattern":"Node '(.+?)': this loop_group's terminal sink \\('(.+?)'\\) 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\\)\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/workflows/src/loader.ts","lineNumber":583,"sourceCode":"      }\n    }\n\n    // A body-terminal sink that is itself an interactive loop/loop_group can pause\n    // without stopping THIS group (#2753) — a bare gate sink is excluded here since\n    // that case is already correctly handled above (and by #2707 step 3 at runtime);\n    // this covers a sink whose own pause is trapped one level down instead.\n    if (bodySinks.length === 1) {\n      const sink = bodySinks[0];\n      if (!isIncludeDirective(sink) && !isGateNode(sink) && isUnescalatableInteractiveSink(sink)) {\n        const message =\n          `Node '${id}': this loop_group's terminal sink ('${sink.id}') is itself an ` +\n          'interactive loop/loop_group — a pause inside it does not escalate to stop ' +\n          \"this loop_group's own iteration (#2753). The outer loop can run further iterations \" +\n          \"while a human's answer to the inner pause is still pending. Only a gate node \" +\n          'directly as the terminal sink correctly stops the enclosing loop_group ' +\n          '(#2707 step 3).';\n        warnings.push(message);\n        getLog().warn(\n          { id, sinkId: sink.id, warning: message },\n          'loop_group_nested_pause_not_escalated'\n        );\n      }\n    }\n  }\n\n  // Recurse into a loop_group body — mirrors collectUnknownNodeKeys's own body\n  // recursion. Resolved and raw body arrays share index order (Zod arrays\n  // preserve it), so they're zipped by position rather than by id.\n  if (isLoopGroupNode(node) && raw !== null && typeof raw === 'object') {\n    const rawGroup = (raw as Record<string, unknown>).loop_group;\n    const rawBody =\n      rawGroup !== null && typeof rawGroup === 'object'\n        ? (rawGroup as Record<string, unknown>).nodes\n        : undefined;\n    if (Array.isArray(rawBody)) {\n      // `id` is unused on the IncludeDirective early-return path above, so a","sourceCodeStart":565,"sourceCodeEnd":601,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/workflows/src/loader.ts#L565-L601","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nouter:\n  loop_group:\n    nodes:\n      - id: step\n        agent: ...\n      - id: inner          # interactive loop_group as sole sink\n        loop_group:\n          nodes:\n            - id: review\n              gate: {}\n# after\nouter:\n  loop_group:\n    nodes:\n      - id: step\n        agent: ...\n      - id: review\n        gate: {}   # gate directly as the outer terminal sink","handlingStrategy":"validation","validationCode":"function sinkIsGate(group) {\n  const dependedOn = new Set(group.nodes.flatMap(n => n.depends_on ?? []));\n  const sinks = group.nodes.filter(n => !dependedOn.has(n.id));\n  return sinks.length === 1 && 'gate' in sinks[0];\n}","typeGuard":"function isGateNode(n) { return typeof n === 'object' && n !== null && 'gate' in n; }","tryCatchPattern":null,"preventionTips":["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"],"tags":["workflow","loop-group","nested-loop","gate-node"],"backgroundTag":"nested-pause-not-escalated","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}