coleam00/Archon · warning

node_on_reject_deprecated

node_on_reject_deprecated

Error message

Node '${id}': 'approval.on_reject' is deprecated. Declare 'approval.decisions' and wire a rework node with "when: \"$${id}.output.decision == 'reject'\"" instead (loop it with loop_group if it should iterate). This gate keeps running via the legacy mechanism until migrated.

What it means

The `approval.on_reject` hook is the legacy mechanism for handling a rejected gate. The current model declares possible outcomes with `approval.decisions` and wires rejection as an ordinary node gated with `when:` on the structured output, optionally looped with loop_group. The loader warns that on_reject still runs via the legacy mechanism until migrated.

Source

Thrown at packages/workflows/src/loader.ts:459

      // no 'decisions:' authored, it still fully controls whether the
      // reviewer's comment becomes the node's output, exactly as before this
      // PR. Warning unconditionally would be false in those cases.
      if (approvalObj.capture_response !== undefined && node.decisionsAuthored) {
        const message =
          `Node '${id}': 'approval.capture_response' is deprecated. Gate output is now ` +
          `always structured as {decision, text} — read '$${id}.output.text' downstream ` +
          'instead. This field is ignored.';
        warnings.push(message);
        getLog().warn({ id, warning: message }, 'node_capture_response_deprecated');
      }
      if (approvalObj.on_reject !== undefined) {
        const message =
          `Node '${id}': 'approval.on_reject' is deprecated. Declare 'approval.decisions' ` +
          `and wire a rework node with "when: \\"$${id}.output.decision == 'reject'\\"" ` +
          'instead (loop it with loop_group if it should iterate). This gate keeps running ' +
          'via the legacy mechanism until migrated.';
        warnings.push(message);
        getLog().warn({ id, warning: message }, 'node_on_reject_deprecated');
      }
    }
  }
  const interactiveLoop =
    (isLoopNode(node) && node.loop.interactive === true) ||
    (isLoopGroupNode(node) && node.loop_group.interactive === true);
  if (interactiveLoop) {
    const message =
      `Node '${id}': node-level loop 'interactive:' is deprecated. A future release ` +
      're-expresses the interactive loop as a gate + loop_group composition (#2707 step 3). ' +
      'Continue using it for now.';
    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

View on GitHub (pinned to 0773b97458)

Solutions

  1. Declare the gate's outcomes with `approval.decisions` (e.g. [approve, reject]).
  2. Remove `on_reject` and wire the rework node with `when: "$<gateId>.output.decision == 'reject'"`, wrapping it in loop_group if the gate should repeat after rework.
  3. Test the reject path after migrating — the legacy mechanism is temporary.
  4. Sweep the workflow library and migrate every on_reject before the legacy path is deleted.

Example fix

# before
approval:
  prompt: "Ship?"
  on_reject:
    node: fix_issues
# after
approval:
  prompt: "Ship?"
  decisions: [approve, reject]
nodes:
  fix_issues:
    when: "$approve.output.decision == 'reject'"
Defensive patterns

Strategy: validation

Validate before calling

// Flag legacy on_reject hooks at author time:
for (const node of workflow.nodes) {
  if (node.type === 'approval' && node.approval?.on_reject !== undefined) {
    throw new Error(`Node '${node.id}': migrate approval.on_reject to approval.decisions + a when:-gated rework node`);
  }
}

Type guard

function usesOnReject(node: { type: string; approval?: Record<string, unknown> }): boolean {
  return node.type === 'approval' && node.approval?.on_reject !== undefined;
}

Prevention

When it happens

Trigger: collectGateAndLoopDeprecationWarnings (via parseDagNode) on a workflow where an approval node defines `approval.on_reject`; fires at parse time on every load of the file (loader.ts:459).

Common situations: Workflows authored before `approval.decisions` existed; copied templates using on_reject; authors wanting rework-after-reject who don't yet know the when:-based pattern.

Related errors


AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01). Data as JSON: /api/errors/b3258d41f9f1e9d8. Report an issue: GitHub.