Skip to content

[Decision] Must a resume failure reach the CALLER in a shape it can act on? — the family question triage reserved, now that its instance cards have each measured their own half and stopped at the same contract #16472

Description

@os-warren

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 statusdata.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 LANDED0cf086759 "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 LANDED1375344b6 (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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions