adopt on a resume even when the harness is a guess

Regression from my own B7 change, reported within the hour: turns collapsing to "turn
completed without output" and coming back only on refresh.

B7 stopped resume-cursor defaulting an unidentified session to claude-code. Correct for the
durable cut-off row, wrong for adoption: useChat sends model only if modelRef.current is
set, so a reconnect without one is routine, not exotic. Declining to adopt left the socket
unbound to the live session, so the running turn output went nowhere — and a refresh looked
like a fix because it rebuilds from the durable log.

Adoption is about DELIVERY and must be generous; only the durable write needs certainty. So
adopt on the default again, mark it as an assumption, and skip the cut-off check on it.
That keeps B7 fixed — no false "agent went away" written against an opencode session — with
no unbound sockets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 17:03:17 +01:00
co-authored by Claude Opus 5
parent ecc10ae6af
commit b07cae142f
2 changed files with 43 additions and 26 deletions
+17 -10
View File
@@ -28,17 +28,24 @@ describe('decideResume', () => {
expect(decideResume(undefined, 'claude-code')).toEqual({ kind: 'adopt', model: 'claude-code' });
});
it('stays unknown rather than defaulting to claude, which is the whole bug', () => {
// No record and no claim. Previously this became `claude-code` and was indistinguishable from a real
// Claude session; now it declines to adopt and declines to run the cut-off check.
expect(decideResume(undefined, undefined)).toEqual({ kind: 'replay-only' });
expect(decideResume(undefined, '')).toEqual({ kind: 'replay-only' });
it('marks a total unknown as an assumption instead of passing it off as knowledge', () => {
// No record and no claim. This still adopts — see below — but the caller can tell it apart, which is
// what keeps the durable cut-off row off a session nobody has identified.
expect(decideResume(undefined, undefined)).toEqual({ kind: 'assume', model: 'claude-code' });
expect(decideResume(undefined, '')).toEqual({ kind: 'assume', model: 'claude-code' });
});
it('never reports replay-only once anything is known, so a live turn is always re-bound', () => {
// The failure mode in the other direction: declining to adopt a session we could have identified
// would silently drop the reconnect that makes turn output resume.
expect(decideResume('claude-code', undefined).kind).toBe('known');
expect(decideResume('', 'claude-code').kind).toBe('adopt');
it('always yields a model to adopt with, because an unbound socket misses the whole turn', () => {
// Regression guard. Refusing to adopt when the harness was unknown looked principled and broke the
// app inside an hour: `useChat.ts` only sends `model` `if (modelRef.current)`, so a reconnect
// without one is routine. The socket never re-bound, the live turn's output went nowhere, and the
// transcript collapsed to "turn completed without output" until a refresh rebuilt it from the log.
for (const d of [
decideResume('claude-code', undefined),
decideResume(undefined, 'opencode/x'),
decideResume(undefined, undefined),
]) {
expect(d.model).toBeTruthy();
}
});
});