fix(claude): reject stale stop after turn settles

This commit is contained in:
Brennan Benson
2026-09-15 10:18:13 -07:00
parent 62d9bb5644
commit 4f61be5ae3
2 changed files with 28 additions and 2 deletions
@@ -77,8 +77,14 @@ export async function cancelClaudeStructuredTurn(input: {
// The translator owns turn identity. A session with no journal has published no
// turn row for a client to name, so it holds no identity this request can contradict.
const ownsRequestedTurn = (): boolean => {
const currentTurnId = session.translator?.currentTurnId ?? null
return currentTurnId === null || currentTurnId === request.turnId
const translator = session.translator
if (!translator) {
return session.dispatchSequence === 0
}
const currentTurnId = translator.currentTurnId
return currentTurnId === null
? session.dispatchSequence === 0
: currentTurnId === request.turnId
}
const isCurrent = (): boolean =>
sessions.get(request.sessionId) === session &&
@@ -109,6 +109,26 @@ describe('Claude turn ownership', () => {
expect(connection.calls.some((call) => call.subtype === 'interrupt')).toBe(true)
})
it('refuses a stale id after the owned turn settles', async () => {
const claude = fakeClaude({ replayUuid: 'echo-turn' })
const { adapter, bodies, connection } = await acquiredWithJournal(claude)
await adapter.dispatch({
sessionId: 'session-1',
clientMessageId: 'client-1',
body: USER_MESSAGE,
fence: 7
})
expect(runningTurnId(bodies)).toBe('echo-turn')
completeTurn(connection, 'result-1')
expect(runningTurnId(bodies)).toBeNull()
await expect(
adapter.cancelTurn({ sessionId: 'session-1', turnId: 'echo-turn', fence: 7 })
).resolves.toEqual({ cancelled: false })
expect(connection.calls.some((call) => call.subtype === 'interrupt')).toBe(false)
})
it('still stops an echo-opened turn', async () => {
const claude = fakeClaude({ replayUuid: 'echo-turn' })
const { adapter, bodies, connection } = await acquiredWithJournal(claude)