{"record":{"id":"b3258d41f9f1e9d8","repo":"coleam00/Archon","slug":"node-on-reject-deprecated","errorCode":"node_on_reject_deprecated","errorMessage":"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.","messagePattern":"Node '(.+?)': 'approval\\.on_reject' is deprecated\\. Declare 'approval\\.decisions' and wire a rework node with \"when: \\\\\"\\$(.+?)\\.output\\.decision == 'reject'\\\\\"\" instead \\(loop it with loop_group if it should iterate\\)\\. This gate keeps running via the legacy mechanism until migrated\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/workflows/src/loader.ts","lineNumber":459,"sourceCode":"      // no 'decisions:' authored, it still fully controls whether the\n      // reviewer's comment becomes the node's output, exactly as before this\n      // PR. Warning unconditionally would be false in those cases.\n      if (approvalObj.capture_response !== undefined && node.decisionsAuthored) {\n        const message =\n          `Node '${id}': 'approval.capture_response' is deprecated. Gate output is now ` +\n          `always structured as {decision, text} — read '$${id}.output.text' downstream ` +\n          'instead. This field is ignored.';\n        warnings.push(message);\n        getLog().warn({ id, warning: message }, 'node_capture_response_deprecated');\n      }\n      if (approvalObj.on_reject !== undefined) {\n        const message =\n          `Node '${id}': 'approval.on_reject' is deprecated. Declare 'approval.decisions' ` +\n          `and wire a rework node with \"when: \\\\\"$${id}.output.decision == 'reject'\\\\\"\" ` +\n          'instead (loop it with loop_group if it should iterate). This gate keeps running ' +\n          'via the legacy mechanism until migrated.';\n        warnings.push(message);\n        getLog().warn({ id, warning: message }, 'node_on_reject_deprecated');\n      }\n    }\n  }\n  const interactiveLoop =\n    (isLoopNode(node) && node.loop.interactive === true) ||\n    (isLoopGroupNode(node) && node.loop_group.interactive === true);\n  if (interactiveLoop) {\n    const message =\n      `Node '${id}': node-level loop 'interactive:' is deprecated. A future release ` +\n      're-expresses the interactive loop as a gate + loop_group composition (#2707 step 3). ' +\n      'Continue using it for now.';\n    warnings.push(message);\n    getLog().warn({ id, warning: message }, 'node_loop_interactive_deprecated');\n  }\n\n  // The prose `until:` completion channel is deprecated for EVERY loop/loop_group,\n  // not only interactive ones (#2707 step 3, \"What gets deleted\"): its one stated\n  // reason to exist — \"the iteration output is a message a human reads at an","sourceCodeStart":441,"sourceCodeEnd":477,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/workflows/src/loader.ts#L441-L477","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Declare the gate's outcomes with `approval.decisions` (e.g. [approve, reject]).","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.","Test the reject path after migrating — the legacy mechanism is temporary.","Sweep the workflow library and migrate every on_reject before the legacy path is deleted."],"exampleFix":"# before\napproval:\n  prompt: \"Ship?\"\n  on_reject:\n    node: fix_issues\n# after\napproval:\n  prompt: \"Ship?\"\n  decisions: [approve, reject]\nnodes:\n  fix_issues:\n    when: \"$approve.output.decision == 'reject'\"","handlingStrategy":"validation","validationCode":"// Flag legacy on_reject hooks at author time:\nfor (const node of workflow.nodes) {\n  if (node.type === 'approval' && node.approval?.on_reject !== undefined) {\n    throw new Error(`Node '${node.id}': migrate approval.on_reject to approval.decisions + a when:-gated rework node`);\n  }\n}","typeGuard":"function usesOnReject(node: { type: string; approval?: Record<string, unknown> }): boolean {\n  return node.type === 'approval' && node.approval?.on_reject !== undefined;\n}","tryCatchPattern":null,"preventionTips":["Always declare approval.decisions on gates instead of on_reject hooks.","Express rejection handling as ordinary nodes gated with when: on $<gateId>.output.decision.","Use loop_group when a gate should repeat after a rework cycle.","Test the reject path after migrating; the legacy mechanism will eventually be deleted."],"tags":["workflow","deprecation","approval-gate","migration"],"backgroundTag":"deprecated-workflow-field","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}