fix(native-chat): drop the unreachable awaits-user marker flag

The captured flag was dead code. `awaitsUser` could only be true when the
projected status was `attention`, and `attention` hits the `continue` above the
push -- so every marker teardown can ever write carries `false` (QA measured
22 of 22 across two real teardowns). The predicate clause reading it was
unreachable by any production path.

A flag that is structurally always false is worse than no flag: it reads as a
safeguard, so the next person to touch this trusts it. The asymmetry it was
added to close was only ever reachable by fault injection, because teardown is
the sole writer of markers and already refuses attention sessions.

Removing it also drops an upgrade discontinuity: as a required field it made a
marker written by the previous build fail validation and be silently discarded,
costing a resume offer on precisely the upgrade where the user was mid-turn.
Markers predating the providerHandleRoot rename still will not parse, but those
carry a leaf-sensitive key the predicate would refuse anyway, so nothing usable
is lost.

In its place the teardown gate now states that `status !== 'working'` is the
SINGLE gate for awaiting-user sessions, why a predicate-side mirror would be
unreachable, and why it could not even re-derive the fact -- so the reasoning is
inherited rather than rediscovered.

Ablation is back to twelve guards; every other clause is unchanged.
This commit is contained in:
Brennan Benson
2026-09-15 14:30:14 -07:00
parent 1c9331e7ea
commit cbd2f6a1c7
5 changed files with 7 additions and 30 deletions
@@ -84,12 +84,6 @@ export function structuredAgentSessionResumableSet(
if (turn.state !== 'interrupted' && turn.state !== 'unverifiable') {
continue
}
// Read off the MARKER, never re-derived. Teardown cancels the pending prompt a few phases after
// it writes the marker, so by now the live journal no longer reports `attention` for exactly the
// sessions this refuses — which is what made the re-derived version inert.
if (marker.awaitsUser) {
continue
}
candidates.push({
sessionId: marker.sessionId,
workspaceId: record.location.workspaceId,
@@ -136,7 +136,6 @@ function marker(overrides: Partial<AgentSessionResumeMarker> = {}): AgentSession
recordedAt: NOW,
trigger: 'quit',
providerHandleRoot: HANDLE_ROOT,
awaitsUser: false,
...overrides
}
}
@@ -175,7 +174,6 @@ describe('deriving what was working at teardown', () => {
turnId: 'turn-1',
recordedAt: NOW,
trigger: 'quit',
awaitsUser: false,
providerHandleRoot: HANDLE_ROOT
}
])
@@ -367,13 +365,6 @@ describe('the resumable set', () => {
)
})
// The flag is CAPTURED at teardown because teardown then cancels the prompt: by the time this
// predicate runs, the live journal no longer reports `attention`, so only the recorded value can
// still refuse. Re-deriving it here was inert for exactly the sessions it was written for.
it('refuses a marker recorded while the chat was blocked on the user', () => {
expect(resumableSet({ markers: [marker({ awaitsUser: true })] })).toEqual([])
})
it('offers a turn whose end the host could not verify', () => {
expect(
resumableSet({ markers: [marker()], items: [turnItem('turn-1', 'unverifiable')] })
@@ -539,7 +530,6 @@ describe('the restart-resume surface', () => {
turnId: 'turn-2',
recordedAt: NOW,
trigger: 'update',
awaitsUser: false,
providerHandleRoot: HANDLE_ROOT
}
])
@@ -44,12 +44,16 @@ export function structuredAgentSessionsWorkingAtTeardown(input: {
}
const snapshot = session.journal.snapshot()
const status = projectStructuredAgentSessionStatus(snapshot.items, snapshot.submissions)
// Captured HERE, while it is still true. A later teardown phase cancels the pending prompt, so
// nothing downstream can re-derive this fact — the marker has to carry it.
const awaitsUser = status === 'attention'
// The product's own classification, so the marker rule cannot disagree with what the UI calls
// working. A turn blocked on an approval or a question projects as `attention`: the agent is
// waiting on the USER, and that is not interrupted work to hand back.
//
// This is the SINGLE gate for those sessions, and deliberately has no mirror in the launch-side
// predicate. Teardown is the only writer of markers and `attention` exits here, so no marker for
// such a session is ever minted and a predicate-side clause would be unreachable. It could not
// even re-derive the fact — a later teardown phase cancels the pending prompt — so it would have
// to be a captured flag, and a field that is structurally always false reads as a safeguard
// while guarding nothing. Do not re-add one.
if (status !== 'working') {
continue
}
@@ -68,7 +72,6 @@ export function structuredAgentSessionsWorkingAtTeardown(input: {
turnId,
recordedAt: input.now,
trigger: input.trigger,
awaitsUser,
// Root, not key: the close path advances Claude's leaf moments after this runs, and a key
// comparison would then refuse the session forever.
providerHandleRoot: agentSessionProviderHandleRoot(head.handle)
@@ -22,7 +22,6 @@ function marker(overrides: Partial<AgentSessionResumeMarker> = {}): AgentSession
recordedAt: NOW,
trigger: 'quit',
providerHandleRoot: 'codex:"thread-1"',
awaitsUser: false,
...overrides
}
}
@@ -34,14 +34,6 @@ export type AgentSessionResumeMarker = {
* preserve — a resume that changes it forked — which is exactly what this guard is for.
*/
providerHandleRoot: string
/**
* Whether the chat was blocked on the USER — a pending approval or question — at teardown.
*
* CAPTURED, never re-derived, because teardown itself destroys the evidence: a later phase
* cancels the pending prompt, so by the next launch the projection no longer reports `attention`
* for precisely the sessions this exists to refuse. Re-reading it was inert.
*/
awaitsUser: boolean
}
const MAX_FIELD_LENGTH = 512
@@ -59,7 +51,6 @@ export function isAgentSessionResumeMarker(value: unknown): value is AgentSessio
isMarkerField(marker.sessionId) &&
isMarkerField(marker.turnId) &&
isMarkerField(marker.providerHandleRoot) &&
typeof marker.awaitsUser === 'boolean' &&
Number.isSafeInteger(marker.recordedAt) &&
(marker.recordedAt as number) >= 0 &&
(marker.trigger === 'quit' || marker.trigger === 'update')