Skip to content

Commit dcc7830

Browse files
committed
Reseed the crash handle when rotation repoints the session
1 parent 4ce1418 commit dcc7830

3 files changed

Lines changed: 29 additions & 4 deletions

File tree

src/tui/runner/exit.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -591,11 +591,25 @@ export async function createRunLifecycle(
591591
"session-rotation",
592592
);
593593
state.sessionId = generateSessionId();
594-
// Repointed, not cleared: the process lives on, so the crash handler
595-
// must keep finding this handle and close out the *new* session.
596-
services.activeRunHandle.sessionId = state.sessionId;
597594
state.startedAt = Date.now();
598595
state.runTaskTitle = state.config.task;
596+
const rotatedBundle = services.buildSessionSources();
597+
// Repointed, not cleared: the process lives on, so the crash handler
598+
// must keep finding this handle and close out the *new* session. The
599+
// fields it copies reseed with the repoint — a crash inside
600+
// initSessionDir/buildAgent below would otherwise stamp the outgoing
601+
// session's turnsUsed (and task, startedAt, model) onto a session
602+
// that has run zero turns.
603+
services.activeRunHandle.sessionId = state.sessionId;
604+
syncRunStateHandle(services.activeRunHandle, {
605+
turnsUsed: 0,
606+
task:
607+
state.runTaskTitle.trim().length > 0
608+
? state.runTaskTitle.trim()
609+
: "(conversation)",
610+
startedAt: state.startedAt,
611+
model: `${rotatedBundle.selected.id}:${rotatedBundle.selected.model}`,
612+
});
599613
services.emitter.emit(
600614
"session.title",
601615
state.runTaskTitle.trim().length > 0
@@ -604,7 +618,6 @@ export async function createRunLifecycle(
604618
);
605619
state.workdir = sessionContextDir(state.config.cwd, state.sessionId);
606620
await initSessionDir(state.config.cwd, state.sessionId);
607-
const rotatedBundle = services.buildSessionSources();
608621
state.liveSources = rotatedBundle.sources;
609622
state.liveDefaultSource = rotatedBundle.defaultSource;
610623
state.liveSource = rotatedBundle.selected;

tests/fixtures/crash-run/simulate-crash.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { installCrashHandlers } from "../../../src/index.js";
77
import {
88
setActiveRun,
99
setTestWriteGate,
10+
syncRunStateHandle,
1011
} from "../../../src/session/active-run.js";
1112
import { sessionDir } from "../../../src/session/index.js";
1213
import { finalizeRunState, saveState } from "../../../src/session/state.js";
@@ -70,6 +71,15 @@ if (rotatedSessionId !== undefined) {
7071
await saveState(cwd, sessionId, rotationState);
7172
}
7273
activeRunHandle.sessionId = rotatedSessionId;
74+
// Matches runner.ts reseeding the handle's snapshot fields at repoint: the
75+
// new session has run zero turns, so a crash before its first persist must
76+
// carry 0, not the outgoing session's count.
77+
syncRunStateHandle(activeRunHandle, {
78+
turnsUsed: 0,
79+
task,
80+
startedAt,
81+
model,
82+
});
7383
activeSessionId = rotatedSessionId;
7484
}
7585

tests/integration/crash-finalize.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ describe("integration — crash finalizes run.json", () => {
9898
readFileSync(outgoingRunJsonPath, "utf8"),
9999
) as RunState;
100100
expect(outgoingState.status).toBe("done");
101+
expect(outgoingState.turnsUsed).toBe(3);
101102

102103
const rotatedRunJsonPath = join(stdout.trim(), "run.json");
103104
const rotatedState = JSON.parse(
@@ -109,6 +110,7 @@ describe("integration — crash finalizes run.json", () => {
109110
expect(rotatedState.status).toBe("crashed");
110111
expect(rotatedState.finishedAt).toBeGreaterThan(0);
111112
expect(rotatedState.error).toContain("simulated crash");
113+
expect(rotatedState.turnsUsed).toBe(0);
112114
} finally {
113115
rmSync(cwd, { recursive: true, force: true });
114116
rmSync(home, { recursive: true, force: true });

0 commit comments

Comments
 (0)