From 10d4098ed848907648296e84cf6caca30fb7b75c Mon Sep 17 00:00:00 2001 From: brennanb2025 Date: Sun, 28 Jun 2026 13:57:44 -0700 Subject: [PATCH] Cancel provisional done when suppressing Codex permission pause A suppressed Codex auto-approval waiting/blocked payload returned before the attention branch's clearPendingHookDone(), so a provisional 'done' scheduled just before the pause could still fire a false completion notification after the quiet window. Cancel the pending done for suppressed attention states. Co-authored-by: Orca --- .../agent-completion-coordinator.test.ts | 39 +++++++++++++++++++ .../agent-completion-coordinator.ts | 5 +++ 2 files changed, 44 insertions(+) diff --git a/src/renderer/src/components/terminal-pane/agent-completion-coordinator.test.ts b/src/renderer/src/components/terminal-pane/agent-completion-coordinator.test.ts index f41518e291d..bcdeeb1c42f 100644 --- a/src/renderer/src/components/terminal-pane/agent-completion-coordinator.test.ts +++ b/src/renderer/src/components/terminal-pane/agent-completion-coordinator.test.ts @@ -1239,6 +1239,45 @@ describe('agent completion coordinator', () => { ) }) + it('cancels a pending done timer when a suppressed attention state arrives before the quiet window', () => { + // Why: a suppressed Codex auto-approval pause must still cancel a provisional + // 'done' so the quiet-window timer never fires a false completion notification. + const dispatchCompletion = vi.fn() + const dispatchAttention = vi.fn() + const coordinator = createAgentCompletionCoordinator({ + paneKey: 'tab-1:leaf-1', + getPtyId: () => 'pty-1', + getSettings: () => null, + inspectProcess: vi.fn(), + dispatchCompletion, + dispatchAttention, + isLive: () => true, + shouldSuppressHookCompletion: (payload) => + payload.state === 'waiting' || payload.state === 'blocked' + }) + + const turn = { + prompt: 'implement notifications', + agentType: 'codex' as const + } + + coordinator.observeHookStatus({ state: 'working', ...turn }) + coordinator.observeHookStatus({ state: 'done', ...turn, lastAssistantMessage: 'Done.' }) + expect(coordinator.hasPendingHookDoneCompletion()).toBe(true) + + coordinator.observeHookStatus({ + state: 'waiting', + ...turn, + toolName: 'exec_command', + toolInput: 'git status' + }) + expect(coordinator.hasPendingHookDoneCompletion()).toBe(false) + vi.advanceTimersByTime(HOOK_DONE_QUIET_MS) + + expect(dispatchCompletion).not.toHaveBeenCalled() + expect(dispatchAttention).not.toHaveBeenCalled() + }) + it('keeps a generic title completion pending long enough for the first remote inspection', async () => { const inspection = createDeferred() const dispatchCompletion = vi.fn() diff --git a/src/renderer/src/components/terminal-pane/agent-completion-coordinator.ts b/src/renderer/src/components/terminal-pane/agent-completion-coordinator.ts index 64eb34ca544..f1aac9f3922 100644 --- a/src/renderer/src/components/terminal-pane/agent-completion-coordinator.ts +++ b/src/renderer/src/components/terminal-pane/agent-completion-coordinator.ts @@ -653,6 +653,11 @@ export function createAgentCompletionCoordinator( function observeHookStatus(payload: AgentCompletionStatusSnapshot): void { if (options.shouldSuppressHookCompletion?.(payload)) { + // Why: a suppressed permission pause must still cancel a provisional 'done' + // so the quiet-window timer never fires a false completion notification. + if (isAttentionHookState(payload.state)) { + clearPendingHookDone() + } return } if (isRecognizedAgentType(payload.agentType)) {