From fd95eb9962fa35a7720adf68a7d00b65d26e8113 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 30 Jun 2025 08:16:28 +0000 Subject: [PATCH] Fix #5179: Add timeout to shell execution complete event - Add timeout mechanism to shellExecutionComplete promise in TerminalProcess - Prevents indefinite hanging when VSCode's onDidEndTerminalShellExecution event doesn't fire - Common issue on Mac systems where commands complete but event is not emitted - Assumes success (exitCode: 0) after timeout to allow execution to proceed - Add comprehensive test coverage for timeout scenario - Uses existing shell integration timeout configuration --- src/integrations/terminal/TerminalProcess.ts | 13 +++++- .../__tests__/TerminalProcess.spec.ts | 46 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/integrations/terminal/TerminalProcess.ts b/src/integrations/terminal/TerminalProcess.ts index eb0424fe8d..46eb2a53d5 100644 --- a/src/integrations/terminal/TerminalProcess.ts +++ b/src/integrations/terminal/TerminalProcess.ts @@ -101,7 +101,18 @@ export class TerminalProcess extends BaseTerminalProcess { // Create promise that resolves when shell execution completes for this terminal const shellExecutionComplete = new Promise((resolve) => { - this.once("shell_execution_complete", (details: ExitCodeDetails) => resolve(details)) + const timeoutId = setTimeout(() => { + console.warn( + "[TerminalProcess] Shell execution complete event not received within timeout, assuming success. This may indicate a VSCode shell integration issue on this platform.", + ) + // Assume success if we don't get the event (common on Mac) + resolve({ exitCode: 0 }) + }, Terminal.getShellIntegrationTimeout()) + + this.once("shell_execution_complete", (details: ExitCodeDetails) => { + clearTimeout(timeoutId) + resolve(details) + }) }) // Execute command diff --git a/src/integrations/terminal/__tests__/TerminalProcess.spec.ts b/src/integrations/terminal/__tests__/TerminalProcess.spec.ts index 04c31bd93a..cf2fd3ef4a 100644 --- a/src/integrations/terminal/__tests__/TerminalProcess.spec.ts +++ b/src/integrations/terminal/__tests__/TerminalProcess.spec.ts @@ -165,6 +165,52 @@ describe("TerminalProcess", () => { await completePromise expect(terminalProcess.isHot).toBe(false) }) + + it("handles missing shell_execution_complete event with timeout", async () => { + // Temporarily suppress the expected console.warn for this test + const consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}) + + let completedOutput: string | undefined + + terminalProcess.on("completed", (output) => { + completedOutput = output + }) + + // Mock stream data with shell integration sequences but NO shell_execution_complete event + mockStream = (async function* () { + yield "\x1b]633;C\x07" // Command start sequence + yield "Command output\n" + yield "More output" + yield "\x1b]633;D\x07" // Command end sequence + // NOTE: We intentionally do NOT emit "shell_execution_complete" to simulate the Mac issue + })() + + mockTerminal.shellIntegration.executeCommand.mockReturnValue({ + read: vi.fn().mockReturnValue(mockStream), + }) + + // Set a very short timeout for testing (override the default) + vi.spyOn(Terminal, "getShellIntegrationTimeout").mockReturnValue(100) + + const runPromise = terminalProcess.run("test command") + terminalProcess.emit("stream_available", mockStream) + + // Wait for the command to complete via timeout + await runPromise + + // Verify the command completed successfully despite missing event + expect(completedOutput).toBe("Command output\nMore output") + expect(terminalProcess.isHot).toBe(false) + + // Verify warning was logged + expect(consoleWarnSpy).toHaveBeenCalledWith( + "[TerminalProcess] Shell execution complete event not received within timeout, assuming success. This may indicate a VSCode shell integration issue on this platform.", + ) + + // Restore mocks + consoleWarnSpy.mockRestore() + vi.restoreAllMocks() + }) }) describe("continue", () => {