{"record":{"id":"f2ae7222cae474be","repo":"coleam00/Archon","slug":"loop-group-gate-completion-not-referenced","errorCode":"loop_group_gate_completion_not_referenced","errorMessage":"Node '${gate.id}': this gate is the loop_group's terminal sink, but 'loop_group.until_bash' does not reference '${gateRef}' — the human's decision is captured but never consulted for completion, so the loop only ends via max_iterations. Declare 'until_bash' checking '${gateRef}.decision' (e.g. '[ \"${gateRef}.decision\" = \"approve\" ]') so the gate's answer actually drives completion (#2707 step 3).","messagePattern":"Node '(.+?)': this gate is the loop_group's terminal sink, but 'loop_group\\.until_bash' does not reference '(.+?)' — the human's decision is captured but never consulted for completion, so the loop only ends via max_iterations\\. Declare 'until_bash' checking '(.+?)\\.decision' \\(e\\.g\\. '\\[ \"(.+?)\\.decision\" = \"approve\" \\]'\\) so the gate's answer actually drives completion \\(#2707 step 3\\)\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/workflows/src/loader.ts","lineNumber":560,"sourceCode":"        // string reference the gate's node id), not prose-sniffing — no judgment\n        // about what the check DOES with it, only whether it looks at it at all.\n        const untilBash = node.loop_group.until_bash;\n        const untilBashRefsGate =\n          untilBash !== undefined &&\n          Array.from(untilBash.matchAll(new RegExp(OUTPUT_REF_SOURCE, 'g'))).some(\n            m => m[1] === gate.id\n          );\n        if (!untilBashRefsGate) {\n          const gateRef = `$${gate.id}.output`;\n          const message =\n            `Node '${gate.id}': this gate is the loop_group's terminal sink, but ` +\n            `'loop_group.until_bash' does not reference '${gateRef}' — the human's ` +\n            'decision is captured but never consulted for completion, so the loop only ' +\n            `ends via max_iterations. Declare 'until_bash' checking '${gateRef}.decision' ` +\n            `(e.g. '[ \"${gateRef}.decision\" = \"approve\" ]') so the gate's answer actually ` +\n            'drives completion (#2707 step 3).';\n          warnings.push(message);\n          getLog().warn(\n            { id: gate.id, warning: message },\n            'loop_group_gate_completion_not_referenced'\n          );\n        }\n      }\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 \" +","sourceCodeStart":542,"sourceCodeEnd":578,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/workflows/src/loader.ts#L542-L578","documentation":"This loader warning fires when a gate node correctly is a loop_group body's sole terminal sink, but the group's loop_group.until_bash expression never references that gate's output ($<gateId>.output). The human's decision is captured (a node_completed event is written), but the completion check never consults it, so the loop only terminates via max_iterations instead of the human's answer (#2707 step 3). Detection is purely structural: the until_bash string is scanned for output references naming the gate id.","triggerScenarios":"Loading a workflow YAML with a loop_group whose body ends in a valid gate sink while loop_group.until_bash is undefined, references other nodes' outputs only, or references the gate with a misspelled/incorrectly-scoped id so the OUTPUT_REF_SOURCE regex match finds no id equal to gate.id.","commonSituations":"Authors declaring until_bash against a work node's output while forgetting the gate; typos in $gate-id.output.decision; copying an until_bash template from a non-gate loop; assuming the gate itself ends the loop without wiring the completion check.","solutions":["Add or extend loop_group.until_bash so it reads the gate's output, e.g. until_bash: '[ \"$approve.output.decision\" = \"approve\" ]'","Fix the node id or output path in until_bash so the reference exactly matches the gate node's id","If the loop is genuinely max_iterations-driven, remove the gate or document that its answer does not drive completion"],"exampleFix":"# before\nloop_group:\n  until_bash: '[ \"$work.output.done\" = \"true\" ]'\n  nodes:\n    - id: work\n      agent: ...\n    - id: approve\n      gate: {}\n# after\nloop_group:\n  until_bash: '[ \"$approve.output.decision\" = \"approve\" ]'\n  nodes:\n    - id: work\n      agent: ...\n    - id: approve\n      gate: {}","handlingStrategy":"validation","validationCode":"function gateReferencedInUntilBash(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  const gate = group.nodes.find(n => 'gate' in n);\n  if (!gate || sinks.length !== 1 || sinks[0].id !== gate.id) return true; // other warning\n  return group.until_bash !== undefined && group.until_bash.includes(`$${gate.id}.output`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Whenever a loop_group body ends in a gate, immediately write until_bash referencing $<gateId>.output.decision","Copy the loader's suggested example form: '[ \"$<gateId>.output.decision\" = \"approve\" ]'","Check node ids character-for-character — the structural check is an exact id match","Test that a rejecting decision actually ends the loop before relying on it in production"],"tags":["workflow","loop-group","gate-node","until-bash"],"backgroundTag":"gate-decision-not-referenced","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}