fix(codex): keep terminal idle backstop gated during in-flight app-server requests

This commit is contained in:
Ayaan Zaidi 2026-07-09 12:38:29 +05:30
parent 4af2ca0a4d
commit 8589759818
2 changed files with 26 additions and 22 deletions

View file

@ -171,35 +171,29 @@ describe("Codex app-server attempt turn watches", () => {
expect(harness.abortController.signal.aborted).toBe(false);
});
it("fires terminal idle timeout while an app-server request is in flight", () => {
it("keeps terminal idle gated while an app-server request is in flight", () => {
const harness = createController();
harness.activeRequests = 1;
harness.controller.armTerminalIdleWatch();
vi.advanceTimersByTime(10);
expect(harness.timeouts).toMatchObject([
{
kind: "terminal",
idleMs: 10,
timeoutMs: 10,
lastActivityReason: "startup",
},
]);
expect(harness.abortController.signal.reason).toBe("turn_terminal_idle_timeout");
expect(harness.timeouts).toEqual([]);
expect(harness.abortController.signal.aborted).toBe(false);
});
it("keeps terminal idle alive when notifications arrive during an in-flight request", () => {
it("fires terminal idle after the in-flight request settles and silence resumes", () => {
const harness = createController();
harness.activeRequests = 1;
harness.controller.armTerminalIdleWatch();
vi.advanceTimersByTime(9);
harness.controller.noteNotificationReceived("item/commandExecution/outputDelta");
vi.advanceTimersByTime(9);
vi.advanceTimersByTime(10);
expect(harness.timeouts).toEqual([]);
harness.activeRequests = 0;
harness.controller.touchActivity("request:item/tool/call:response");
vi.advanceTimersByTime(9);
expect(harness.timeouts).toEqual([]);
expect(harness.abortController.signal.aborted).toBe(false);
vi.advanceTimersByTime(1);
expect(harness.timeouts).toMatchObject([
@ -207,9 +201,10 @@ describe("Codex app-server attempt turn watches", () => {
kind: "terminal",
idleMs: 10,
timeoutMs: 10,
lastActivityReason: "notification:item/commandExecution/outputDelta",
lastActivityReason: "request:item/tool/call:response",
},
]);
expect(harness.abortController.signal.reason).toBe("turn_terminal_idle_timeout");
});
it("keeps completion idle gated while a request is in flight", () => {

View file

@ -166,7 +166,12 @@ export function createCodexAttemptTurnWatchController(params: {
function scheduleTerminalIdleWatch() {
clearTerminalIdleTimer();
if (params.isCompleted() || params.signal.aborted || !terminalIdleWatchArmed) {
if (
params.isCompleted() ||
params.signal.aborted ||
!terminalIdleWatchArmed ||
params.getActiveAppServerTurnRequests() > 0
) {
return;
}
const elapsedMs = Math.max(0, Date.now() - completionLastActivityAt);
@ -366,14 +371,18 @@ export function createCodexAttemptTurnWatchController(params: {
}
function fireTerminalIdleTimeout() {
// This is the physical-client liveness backstop. An in-flight request can
// be silent forever if the client is wedged, so only real notifications
// reset the terminal clock.
// Physical-client liveness backstop. A terminal timeout retires the shared
// client, so it must only measure silence the client owns: while a
// server->client request is pending (approval/elicitation/tool call) the
// app-server legitimately says nothing until we respond. The response path
// touches activity when the request settles, so a wedged client is still
// caught within one terminal window after our response.
if (
params.isCompleted() ||
params.isTerminalTurnNotificationQueued() ||
params.signal.aborted ||
!terminalIdleWatchArmed
!terminalIdleWatchArmed ||
params.getActiveAppServerTurnRequests() > 0
) {
return;
}