coleam00/Archon · warning
node_loop_until_deprecated
node_loop_until_deprecated
Error message
Node '${id}': the prose 'loop.until' completion signal is deprecated. Declare 'loop.until_bash' (deterministic check) or 'loop.until_field' (a declared boolean in output_format) instead (#2707 step 3). While supported, emit legacy signals as '<promise>SIGNAL</promise>' or a final standalone signal line. What it means
The prose `loop.until` completion signal — the model emits a free-text signal the loop matches — is deprecated in favor of deterministic checks: `loop.until_bash` (a bash predicate) or `loop.until_field` (a declared boolean in the node's output_format). The loader warns; prose signals still work while supported, emitted as `<promise>SIGNAL</promise>` or a final standalone signal line.
Source
Thrown at packages/workflows/src/loader.ts:489
warnings.push(message);
getLog().warn({ id, warning: message }, 'node_loop_interactive_deprecated');
}
// The prose `until:` completion channel is deprecated for EVERY loop/loop_group,
// not only interactive ones (#2707 step 3, "What gets deleted"): its one stated
// reason to exist — "the iteration output is a message a human reads at an
// interactive gate" (#2563) — evaporates once that human interaction is a gate
// node with structured decision output rather than a prose sentinel. `until_bash`
// and (loop: only) `until_field` are the declared, structured replacements. This
// keeps running exactly as before; only the guidance is new.
if (isLoopNode(node) && node.loop.until !== undefined) {
const message =
`Node '${id}': the prose 'loop.until' completion signal is deprecated. Declare ` +
"'loop.until_bash' (deterministic check) or 'loop.until_field' (a declared boolean " +
'in output_format) instead (#2707 step 3). While supported, emit legacy signals as ' +
"'<promise>SIGNAL</promise>' or a final standalone signal line.";
warnings.push(message);
getLog().warn({ id, warning: message }, 'node_loop_until_deprecated');
} else if (isLoopGroupNode(node) && node.loop_group.until !== undefined) {
const message =
`Node '${id}': the prose 'loop_group.until' completion signal is deprecated. ` +
"Declare 'loop_group.until_bash' instead — it can read a body node's structured " +
'output (e.g. \'test $body-node.output.field = "true"\') (#2707 step 3). While ' +
"supported, emit legacy signals as '<promise>SIGNAL</promise>' or a final standalone " +
'signal line.';
warnings.push(message);
getLog().warn({ id, warning: message }, 'node_loop_group_until_deprecated');
}
// A gate node inside a loop_group body only pauses the enclosing loop when it is
// the body's SOLE terminal sink (#2707 step 3, target-model decision (b)) — a
// mid-body or co-terminal gate has no defined resume semantics and silently does
// not stop iteration (mid-body resume-to-node is deferred to #2708). This is
// guidance for the new authoring pattern, not a rejection: the file keeps loading
// either way, matching the grow-then-deprecate posture used throughout this
// function — including for a gate placed here via the legacy `on_reject`View on GitHub (pinned to 0773b97458)
Solutions
- Replace `loop.until: SIGNAL` with `loop.until_bash:` running a deterministic predicate against the node's structured output.
- Or declare a boolean in the loop body's output_format and use `loop.until_field:` on it.
- If keeping prose until temporarily, ensure the signal is emitted as `<promise>SIGNAL</promise>` or a final standalone signal line.
- Migrate before the prose channel is deleted (#2707 step 3) — after removal it will stop matching with no fallback.
Example fix
# before loop: until: TASK_COMPLETE # after loop: until_field: done # body declares `done: boolean` in output_format
Defensive patterns
Strategy: validation
Validate before calling
// Require deterministic loop completion before loading:
for (const node of workflow.nodes) {
if (node.type === 'loop' && node.loop?.until !== undefined) {
throw new Error(`Node '${node.id}': replace prose loop.until with until_bash or until_field`);
}
} Type guard
function usesProseUntil(node: { type: string; loop?: { until?: unknown } }): boolean {
return node.type === 'loop' && node.loop?.until !== undefined;
} Prevention
- Default new loops to until_bash/until_field; never author prose until signals.
- Declare completion booleans in the loop body's output_format so checks are deterministic.
- If keeping prose until temporarily, enforce the <promise>SIGNAL</promise> emission form in prompts.
- Re-verify loops after model/provider changes — prose signals are the first thing rewording breaks.
When it happens
Trigger: collectGateAndLoopDeprecationWarnings on a loop node whose `loop.until` is defined (loader.ts:489); fires at parse time. Prose matching is fragile because vendor/model rewording can silently break signal detection.
Common situations: Loops authored before until_bash/until_field existed; loops relying on the model to declare 'DONE'; intermittent non-termination because the model phrased the completion signal differently.
Related errors
- node_loop_interactive_deprecated
- node_loop_group_until_deprecated
- Loop-group node '${node.id}' failed at iteration ${String(i)
- Loop-group node '${node.id}' exceeded max iterations (${Stri
- Loop node '${node.id}' exceeded max iterations (${String(loo
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/8611a05212710b0c.
Report an issue: GitHub.