You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
the onEmptyApprovers: 'fallback' / fallbackApprovers pairing is statically provable but has no lint arm — it is refused only at node entry, so the typo class stays a run-time discovery #18873
⛔ Recorded for triage; no severity asserted, no domain:*, no type — routing and grading are triage's. Filed by the domain:devx execution PM seat (post #6023, session session_017ef78bLdybu3AffehKkhfk), round 38, as arm (b) of #18551. ⛔ Not claiming.
⚠️This card exists because #18551's own text forbade dropping it. That card offered two arms and said, verbatim: 「⛔ Do not do (a) and silently drop (b): if (b) is wanted, (a)'s wording has to be re-reverted when it lands. Whoever takes this should say which one they are doing.」 ⇒ The seat took (a) (PR #18868, wording only) and declared (b) would get its own carrier. This is that carrier.
The gap
The pairing between onEmptyApprovers: 'fallback' and a sibling fallbackApprovers list is enforced in both directions — and only at flow execution, when the approval node parses its config on entry. Read on origin/main @ a09d79708 at 2026-09-18T03:11Z:
packages/spec/src/automation/approval.zod.ts:728 function checkFallbackApproversPairing(
packages/spec/src/automation/approval.zod.ts:947 }).superRefine(checkFallbackApproversPairing));
packages/plugins/plugin-approvals/src/approval-node.ts:133
const parsed = ApprovalNodeConfigSchema.safeParse(node.config ?? {}); ← the only non-test parser
And no author-time arm exists: packages/lint/src/validate-approval-approvers.ts declares 10 rule ids and none of them is the pairing —
The pairing is statically provable.#18551 measured the reason in one line: both keys sit in the same object. ⇒ Nothing about this needs run-time knowledge — unlike its neighbour at approvals.mdx:27, where whether a write carries a user is genuinely only knowable at run time, and the page says so correctly.
⇒ Building it moves a whole typo class from run time to author time. Today the author learns at the moment a real flow reaches a real approval node; with the arm, os lint says it before anything runs.
⚠️The current behaviour is not a hole — triage graded #18551 p3 on exactly that: 「契约是被兑现的,只是时刻不同 …… 拒绝是响亮的、点名两个键、且在任何审批请求打开之前」. ⇒ This card is an improvement, ⛔ not a defect report, and it should be graded as one.
⛔ Why the seat did not just dispatch it
It sits on the manual floor: SKILL.md's floor list names 「新增必需门禁/hook/棘轮」. A new author-time rule that fails a run is a new gate, and the floor is a maintainer's to set — ⛔ not a dispatching seat's, and ⛔ not reachable by folding it into a p3 prose fix. Folding it in would also have let the easy half carry the hard half, which is the shape #18551's own text was written to prevent.
What it needs decided before anyone builds it — ⛔ the seat does not pre-empt these
Rule id. The 10 above establish the approval-* naming; a pairing arm needs one that does not read as a duplicate of approval-expression-no-empty-policy.
⚠️ The coupling to #18868, stated so it is not discovered later
PR #18868 reworded approvals.mdx to say the refusal fires at flow execution and that os lint has no pairing arm for this one. ⇒ If this card is built, that sentence becomes false and must be re-reverted in the same PR.#18551 named this consequence up front; it is recorded here so the coupling travels with the work rather than with anyone's memory.
⛔ What is NOT claimed
⛔ Not that the runtime refusal is inadequate. It is loud, names both keys, and lands before any approval request opens.
⛔ Not that severity should be error — that is item 2 above, undecided.
⛔ Not measured: how many authored flows in this repo or in examples/** would actually trip such a rule today. ⚠️ A zero there would be a real argument against building it, and nobody has looked.
Duplicate check — method stated
One targeted MCP search_issues call over this repository (repo-scoped REST /search/* answers 403 for this seat, so the channel is declared).
⛔ Recorded for triage; no severity asserted, no
domain:*, no type — routing and grading are triage's. Filed by thedomain:devxexecution PM seat (post #6023, sessionsession_017ef78bLdybu3AffehKkhfk), round 38, as arm (b) of #18551. ⛔ Not claiming.The gap
The pairing between
onEmptyApprovers: 'fallback'and a siblingfallbackApproverslist is enforced in both directions — and only at flow execution, when the approval node parses its config on entry. Read onorigin/main@a09d79708at 2026-09-18T03:11Z:And no author-time arm exists:
packages/lint/src/validate-approval-approvers.tsdeclares 10 rule ids and none of them is the pairing —⭐ Why it is worth building rather than leaving
The pairing is statically provable. #18551 measured the reason in one line: both keys sit in the same object. ⇒ Nothing about this needs run-time knowledge — unlike its neighbour at
approvals.mdx:27, where whether a write carries a user is genuinely only knowable at run time, and the page says so correctly.⇒ Building it moves a whole typo class from run time to author time. Today the author learns at the moment a real flow reaches a real approval node; with the arm,
os lintsays it before anything runs.⛔ Why the seat did not just dispatch it
It sits on the manual floor:
SKILL.md's floor list names 「新增必需门禁/hook/棘轮」. A new author-time rule that fails a run is a new gate, and the floor is a maintainer's to set — ⛔ not a dispatching seat's, and ⛔ not reachable by folding it into a p3 prose fix. Folding it in would also have let the easy half carry the hard half, which is the shape #18551's own text was written to prevent.What it needs decided before anyone builds it — ⛔ the seat does not pre-empt these
approval-*naming; a pairing arm needs one that does not read as a duplicate ofapproval-expression-no-empty-policy.errormakes the sentence docs(automation): say when the fallbackApprovers pairing is actually enforced #18868 just corrected true as written again (and that wording would then need reverting — [finding]approvals.mdx:302says the fallback pairing is "refused at authoring time" — nothing parses the node config before flow execution, so the refusal fires at node entry #18551 said so explicitly);warningleaves the doc correct as it now stands.approval-approvers-may-resolve-empty. That rule already reasons about empty approver sets.approval-approvers-may-resolve-emptycoverspositionand notmanager— the rung whose column no product surface can write is the one the rule stays silent on #16748 (closed) records it coveringpositionand notmanager— so its boundary is already known to be uneven, and a new arm must say where it stops rather than overlapping silently.PR #18868 reworded
approvals.mdxto say the refusal fires at flow execution and thatos linthas no pairing arm for this one. ⇒ If this card is built, that sentence becomes false and must be re-reverted in the same PR. #18551 named this consequence up front; it is recorded here so the coupling travels with the work rather than with anyone's memory.⛔ What is NOT claimed
error— that is item 2 above, undecided.examples/**would actually trip such a rule today.Duplicate check — method stated
One targeted MCP
search_issuescall over this repository (repo-scoped REST/search/*answers 403 for this seat, so the channel is declared).onEmptyApproversgains'fallback'with a siblingfallbackApproversat the node level — the empty{ type: 'manager' }rung becomes survivable (from #16678 Phase 2 §8.2, ruled) #17931 (the card that introducedfallbackApprovers) andapproval-approvers-may-resolve-emptycoverspositionand notmanager— the rung whose column no product surface can write is the one the rule stays silent on #16748 (theapproval-approvers-may-resolve-emptycoverage card) both had to hit, and both did ⇒ the zero is a reading, not a dead index.Dedupe words:
fallbackApprovers pairing lint arm·checkFallbackApproversPairing·onEmptyApprovers fallback author-time·approval-approvers-may-resolve-empty scope·approvals.mdx refusal timingRefs
#18551 · PR #18868 · #17931 · #16748 ·
approval.zod.ts:728/:947·approval-node.ts:133Generated by Claude Code