coleam00/Archon · warning
node_loop_group_until_deprecated
node_loop_group_until_deprecated
Error 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. What it means
The prose `loop_group.until` completion signal is deprecated the same way as `loop.until`: declare `loop_group.until_bash` instead, which deterministically reads a body node's structured output (e.g. `test $body-node.output.field = "true"`). The loader warns; while supported, the legacy signal must be emitted as `<promise>SIGNAL</promise>` or a final standalone signal line.
Source
Thrown at packages/workflows/src/loader.ts:498
// 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`
// mechanism, which predates and is unrelated to this pattern but is equally
// unable to stop the loop from this position.
if (isLoopGroupNode(node)) {
// Every body entry — including an unexpanded `include:` directive, which has
// the identical `depends_on` shape (both extend dagNodeBaseSchema) and is a
// real graph participant here, not yet inlined — contributes to and can BE a
// terminal sink. Excluding it would silently misclassify a gate a downstream
// include node depends on as "terminal", and miss an include node that is
// itself a second, co-terminal sink.View on GitHub (pinned to 0773b97458)
Solutions
- Replace `loop_group.until:` with `loop_group.until_bash:` — e.g. `test $body-node.output.field = "true"` reading a body node's declared structured output.
- Declare the boolean the predicate reads in the body node's output_format so completion is deterministic.
- If keeping prose temporarily, use the legacy emission form: `<promise>SIGNAL</promise>` or a final standalone signal line.
- Migrate all loop_group.until usages before the prose channel is removed (#2707 step 3).
Example fix
# before loop_group: body: [run_tests, fix] until: ALL_PASS # after loop_group: body: [run_tests, fix] until_bash: 'test "$run_tests.output.all_passed" = "true"'
Defensive patterns
Strategy: validation
Validate before calling
// Require deterministic loop_group completion before loading:
for (const node of workflow.nodes) {
if (node.type === 'loop_group' && node.loop_group?.until !== undefined) {
throw new Error(`Node '${node.id}': replace prose loop_group.until with loop_group.until_bash`);
}
} Type guard
function usesProseGroupUntil(node: { type: string; loop_group?: { until?: unknown } }): boolean {
return node.type === 'loop_group' && node.loop_group?.until !== undefined;
} Prevention
- Author loop_group completion with until_bash reading a body node's declared structured output.
- Declare the boolean the predicate reads in the body node's output_format.
- If keeping prose until temporarily, enforce the <promise>SIGNAL</promise> or standalone-final-line emission form.
- Sweep multi-node loop bodies for prose until before the prose channel is deleted.
When it happens
Trigger: collectGateAndLoopDeprecationWarnings on a loop_group node whose `loop_group.until` is defined (loader.ts:498); fires at every parse of the workflow.
Common situations: Multi-node loop bodies whose completion was declared in prose; workflows predating until_bash; intermittent non-termination when the model phrases the signal differently than expected.
Related errors
- node_loop_until_deprecated
- ⚠️ `${workflow.name}` is deprecated and will be removed in a
- node_capture_response_deprecated
- node_on_reject_deprecated
- node_loop_interactive_deprecated
AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01).
Data as JSON: /api/errors/e27de13a57a590a6.
Report an issue: GitHub.