Skip to content

Commit be9c359

Browse files
committed
fix(tui): keep both reload clears when merging the parent
A rebuild must clear the rehydrated-approval hold, or the next idle check still treats the session as resuming. Interrupt and session rotation also clear a deferred reload before buildAgent awaits, so a connect during that build can still set the flag. Initial and mcp-tools-changed builds leave pendingReload for reloadIfIdle.
2 parents 230974d + f0d2189 commit be9c359

2 files changed

Lines changed: 44 additions & 0 deletions

File tree

‎src/tui/runner/exit.test.ts‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,43 @@ describe("reload-if-idle waits for the reactor's message run", () => {
11531153
await services.sessionOps.awaitTail();
11541154
expect(builds()).toBe(3);
11551155
});
1156+
1157+
test("a pending reload held for an open run does not survive interrupt", async () => {
1158+
const { state, services, builds } = await lifecycleWithOpenRun();
1159+
state.pendingReload = true;
1160+
defined(state.reloadIfIdle, "reloadIfIdle")();
1161+
await services.sessionOps.awaitTail();
1162+
expect(builds()).toBe(1);
1163+
expect(state.pendingReload).toBe(true);
1164+
defined(state.interrupt, "interrupt")();
1165+
await services.sessionOps.awaitTail();
1166+
expect(state.pendingReload).toBe(false);
1167+
expect(builds()).toBe(2);
1168+
await runWhileAgentBusy(state, async () => undefined);
1169+
expect(builds()).toBe(2);
1170+
expect(state.pendingReload).toBe(false);
1171+
});
1172+
1173+
test("a pending reload held for an open run does not survive session rotation", async () => {
1174+
const { state, services, builds } = await lifecycleWithOpenRun();
1175+
const dirs = stubRotationDirs();
1176+
try {
1177+
state.pendingReload = true;
1178+
defined(state.reloadIfIdle, "reloadIfIdle")();
1179+
await services.sessionOps.awaitTail();
1180+
expect(builds()).toBe(1);
1181+
expect(state.pendingReload).toBe(true);
1182+
defined(state.newSession, "newSession")();
1183+
await services.sessionOps.awaitTail();
1184+
expect(state.pendingReload).toBe(false);
1185+
expect(builds()).toBe(2);
1186+
await runWhileAgentBusy(state, async () => undefined);
1187+
expect(builds()).toBe(2);
1188+
expect(state.pendingReload).toBe(false);
1189+
} finally {
1190+
for (const spy of dirs) spy.mockRestore();
1191+
}
1192+
});
11561193
});
11571194

11581195
// An interrupt can hit close() mid-teardown and leak the workdir lock (see

‎src/tui/runner/exit.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,13 @@ export async function createRunLifecycle(
499499
// mid-run by interrupt or rotation) can never emit its end.
500500
openMessageRuns.clear();
501501
resumingOutsideRun = false;
502+
// Those rebuilds already construct the agent a deferred reload was
503+
// waiting on. Clear before the await so a connect that lands during
504+
// buildAgent can still mark tools this build did not see. Initial and
505+
// reload builds leave the flag to reloadIfIdle.
506+
if (reason === "interrupt" || reason === "session-rotation") {
507+
state.pendingReload = false;
508+
}
502509
state.currentAgent = await services.buildAgent();
503510
};
504511
const agentBusy = (): boolean =>

0 commit comments

Comments
 (0)