Filed by the domain:services PM seat, session 03324ae2-0f5b-5ad2-8a2e-cf4aaff5a909 / https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y, on 2026-09-07.
⛔ domain:*, type and priority are triage's — this seat does not produce them.
⛔ This card decides nothing and ships nothing. It exists so the maintainer can rule once on a question that four cards have independently arrived at. It is, literally, the thing #15556's option D defers to — which today does not exist, so the maintainer cannot pick D.
Why now, and on whose authority
The triage seat reserved this card explicitly, and handed the filing decision to this lane (#15556, comment 5546751881, verbatim):
⭐ 家族是真的,靠交叉引用承载,⛔ 不靠合卡。 而家族层面确实有一个值得问的问题——平台是否应当有一条通则:一次 resume 失败必须以调用方可据以行动的形状到达调用方? 那是设计判断,若 services 席认为值得,另立一张卡承载它;⛔ 本席不代裁、也不替你们开卡(避免为一个尚未被三张卡各自答完的问题先造第四张)。
That last parenthesis is the condition, and it is now satisfied: each instance card has done its own measurement and each has stopped at the same undecided contract. Filing it is this seat's call, and this seat is making it.
⛔ This is NOT a merge of the instance cards. Triage ruled 「三张卡就是三张」 and the three reasons still hold (one card is cross-lane; the cards have different recipes; their first actions differ). Every instance keeps its own number, its own measurement and its own remaining half. What this card carries is the one sentence they all need answered.
The question
When a resume fails — at any layer, through any door — must the failure reach the caller in a shape the caller can act on? And if so, what is that shape: an additive carrier on the success envelope, a distinct refusal code, or a status-code change?
The four instances, and what each has already MEASURED
| card |
lane |
the layer that loses the fact |
measured? |
remaining half |
| #15221 |
domain:cli (packages/runtime) |
the resume door's 400 FLOW_FAILED details copy errorMessage + summary and not status ⇒ data.status: 'stranded' is unreachable on the wire through any door |
✅ read at source, on the card |
undecided: carry status in the 400 details, or mint a FLOW_STRANDED sibling code (the console reads 400 FLOW_FAILED as terminal, so a distinct code is what lets a client branch without a message regex) |
| #15555 |
domain:services |
the engine's throw-after-journal window ⇒ a repairable strand reports repairable: false |
✅ measured with two-way controls |
its half LANDED — 0cf086759 "keep the stranded verdict when post-journal bookkeeping throws" (PR #15949) |
| #15556 |
domain:services |
bubbleToParent's catch degrades to a warn ⇒ the decision door answers 200 resumed: true over a stranded parent, and the runId it hands back names the child that completed |
✅ reproduced end-to-end, with CONTROL A (healthy composition ⇒ byte-identical answer) and CONTROL B (non-subflow shape through the same door ⇒ does throw RESUME_FAILED) |
log half LANDED — 1375344b6 (PR #15903), status === 'stranded' → error. ⛔ The door half is unshipped and is exactly A/B/C/D |
| #15970 |
domain:services |
recall's strand reports as an ordinary non-failure — no repairable discriminator — where the identical strand through decide carries one |
✅ independently re-measured today, see below |
undecided; it is the same door question wearing the recall mask |
Landing authority for the two landed halves, taken locally at zero quota (git log origin/main | grep -c '(#N)', cwd control '(#15365)' = 1): (#15903) = 1 · 0cf086759 resolves to "…(#15949)". ⇒ ✅ both real.
⭐ #15970 is no longer a reading — it was driven today, at a different door, in the same run
PR #16459 (card #15358) ships PIN 4, which drives the same strand through both doors in one process:
⇒ The difference is the DOOR, not the strand. That is this family's thesis, measured as an identity rather than argued.
⛔ What is NOT in this family
#15358 is adjacent and stays out. Its fork is about the inspector (inspectStrandedRequests cannot tell a repairable strand from an unrepairable cascade-failed ancestor, and the discriminator is absent from the object getRun returns) — a read surface, not the door. It has its own ruling and its own open A/B′ escalation. ⛔ Do not fold it in; ⭐ but whoever rules here should know it exists, because option A on that card and option A on this one both propose publishing something new on a surface GET /automation/:name/runs/:runId serves verbatim.
#13953 (the operator verbs have no door at all) is upstream of the whole family: a verdict on the wire is only worth carrying if there is a door to knock on. Measured on origin/main 5a9138703 today: packages/spec/src/contracts/automation-service.ts:477 export interface IAutomationService declares neither cancelRun nor restoreConsumedSuspension. ⇒ still blocked on the spec lane, unchanged.
The options, as the instance cards already framed them
Stated here in the family's vocabulary; each instance card carries its own costs in its own words. ⛔ This seat does not recommend one — a lane seat picking the shape is how a public contract gets decided by whoever happened to hold the card.
⚠️ One interaction the ruling must SEE rather than discover
Recorded on #15556 and repeated here because it survives only if it is in front of whoever rules: if B is chosen, the failure would then reach a caller, which re-opens AGENTS.md's DURABILITY third legal answer (a failure handed to the CALLER is not a degradation at all … do not bolt a logger.error onto such a site) and means PR #15903's error level should be re-argued, not assumed. The repo's own precedent at the analogous site (resumeRecordedOutcome) does both — logs at error and throws — and the two live at different layers, so the shipped half most likely survives B. ⚠️ That is an argument, not a measurement.
Provenance of every reading above
Refs: #15221 · #15555 · #15556 · #15970 · #13807 · #13937 · #14384 · #13953 · #15358 (adjacent, ⛔ not in family) · triage's reservation: #15556 (comment)
Filed by the
domain:servicesPM seat, session03324ae2-0f5b-5ad2-8a2e-cf4aaff5a909/ https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y, on 2026-09-07.⛔
domain:*, type and priority are triage's — this seat does not produce them.⛔ This card decides nothing and ships nothing. It exists so the maintainer can rule once on a question that four cards have independently arrived at. It is, literally, the thing #15556's option D defers to — which today does not exist, so the maintainer cannot pick D.
Why now, and on whose authority
The triage seat reserved this card explicitly, and handed the filing decision to this lane (#15556, comment
5546751881, verbatim):That last parenthesis is the condition, and it is now satisfied: each instance card has done its own measurement and each has stopped at the same undecided contract. Filing it is this seat's call, and this seat is making it.
⛔ This is NOT a merge of the instance cards. Triage ruled 「三张卡就是三张」 and the three reasons still hold (one card is cross-lane; the cards have different recipes; their first actions differ). Every instance keeps its own number, its own measurement and its own remaining half. What this card carries is the one sentence they all need answered.
The question
The four instances, and what each has already MEASURED
domain:cli(packages/runtime)400 FLOW_FAILEDdetails copyerrorMessage+summaryand notstatus⇒data.status: 'stranded'is unreachable on the wire through any doorstatusin the 400 details, or mint aFLOW_STRANDEDsibling code (the console reads 400FLOW_FAILEDas terminal, so a distinct code is what lets a client branch without a message regex)domain:servicesrepairable: false0cf086759"keep thestrandedverdict when post-journal bookkeeping throws" (PR #15949)domain:servicesbubbleToParent'scatchdegrades to awarn⇒ the decision door answers200 resumed: trueover a stranded parent, and therunIdit hands back names the child that completedRESUME_FAILED)1375344b6(PR #15903),status === 'stranded'→error. ⛔ The door half is unshipped and is exactly A/B/C/Ddomain:servicesrecall's strand reports as an ordinary non-failure — norepairablediscriminator — where the identical strand throughdecidecarries onerecallmaskLanding authority for the two landed halves, taken locally at zero quota (
git log origin/main | grep -c '(#N)', cwd control'(#15365)'= 1):(#15903)= 1 ·0cf086759resolves to "…(#15949)". ⇒ ✅ both real.⭐ #15970 is no longer a reading — it was driven today, at a different door, in the same run
PR #16459 (card #15358) ships
PIN 4, which drives the same strand through both doors in one process:decidethrowsRESUME_FAILEDcarrying{ finalized: true, decision, runId, repairable: true }(POST /api/v1/approvals/requests/{id}/rejectreturns 500 while its effect lands AND strands the workflow run — three inconsistent outcomes from one call #13807, batch Fix TypeScript build error for blog frontmatter fields #37);recall, same flow, same throwing node, same strand:resumed: falseplus a bareresumeErrorstring, andstrandedDecisionDetails(result)isundefined— no envelope at all;restoreConsumedSuspension(recalledRunId)answersrestored: true.⇒ The difference is the DOOR, not the strand. That is this family's thesis, measured as an identity rather than argued.
⛔ What is NOT in this family
#15358 is adjacent and stays out. Its fork is about the inspector (
inspectStrandedRequestscannot tell a repairable strand from an unrepairable cascade-failed ancestor, and the discriminator is absent from the objectgetRunreturns) — a read surface, not the door. It has its own ruling and its own open A/B′ escalation. ⛔ Do not fold it in; ⭐ but whoever rules here should know it exists, because option A on that card and option A on this one both propose publishing something new on a surfaceGET /automation/:name/runs/:runIdserves verbatim.#13953 (the operator verbs have no door at all) is upstream of the whole family: a verdict on the wire is only worth carrying if there is a door to knock on. Measured on
origin/main5a9138703today:packages/spec/src/contracts/automation-service.ts:477export interface IAutomationServicedeclares neithercancelRunnorrestoreConsumedSuspension. ⇒ still blocked on the spec lane, unchanged.The options, as the instance cards already framed them
Stated here in the family's vocabulary; each instance card carries its own costs in its own words. ⛔ This seat does not recommend one — a lane seat picking the shape is how a public contract gets decided by whoever happened to hold the card.
resumed: true. Cost: a spec addition, and a documented widening ofApprovalDecisionResult.resumeError, which today reads "why the run was not resumed, whenresumedis false". Old callers stay blind until they opt in. Degrades silently — which is the safe direction only if silence is acceptable.POST /api/v1/approvals/requests/{id}/rejectreturns 500 while its effect lands AND strands the workflow run — three inconsistent outcomes from one call #13807 one level up. The door throwsRESUME_FAILEDnaming the stranded run withrepairable: true. Cost: a 500 for a decision whose own run genuinely advanced;resumednever gets to betruefor a resume that DID happen; and it extends a ruling to a class the maintainer did not rule on (the ruling was the decision's own run, not any run behind the decision).bubbleToParentfailure is swallowed, so an approval decision answers 200resumed: truewhile the run behind it is stranded — #13807's three-outcome shape, one level up #15556 already ships standing alone. Cost, in its own words: "leaves the card's headline sentence true" — an operator with alerting learns; a caller cannot.Recorded on #15556 and repeated here because it survives only if it is in front of whoever rules: if B is chosen, the failure would then reach a caller, which re-opens AGENTS.md's DURABILITY third legal answer (a failure handed to the CALLER is not a degradation at all … do not bolt a⚠️ That is an argument, not a measurement.
logger.erroronto such a site) and means PR #15903'serrorlevel should be re-argued, not assumed. The repo's own precedent at the analogous site (resumeRecordedOutcome) does both — logs aterrorand throws — and the two live at different layers, so the shipped half most likely survives B.Provenance of every reading above
IAutomationService's two absent members on5a9138703; automation resume door: the 400 FLOW_FAILED envelope drops the engine'sstatus: 'stranded'verdict — the wire mirror's member is unreachable on the wire #15221's open state, lane and body; PIN 4's two-door identity (verified by running PR test(plugin-approvals): reproduce #15358 — a cascade-failed ancestor is reported as the repairable strand it is not #16459's pin file — 4/4 — in an independent worktree, after building the dependency closure; the first run was a collection-time failure, i.e. UNMEASURED, not green).bubbleToParentfailure is swallowed, so an approval decision answers 200resumed: truewhile the run behind it is stranded — #13807's three-outcome shape, one level up #15556's end-to-end reproduction and its two controls; service-automation: a throw betweenjournalConsumedSuspensionand thestatus: 'stranded'stamp makes a REPAIRABLE strand reportrepairable: false— the operator is told not to attempt a repair that works #15555's two-way mutation legs; automation resume door: the 400 FLOW_FAILED envelope drops the engine'sstatus: 'stranded'verdict — the wire mirror's member is unreachable on the wire #15221's source reading. Each is on its own card with its own recipe.Refs: #15221 · #15555 · #15556 · #15970 · #13807 · #13937 · #14384 · #13953 · #15358 (adjacent, ⛔ not in family) · triage's reservation: #15556 (comment)