From 86a508231034855cc5b0a2857ec9a29c1a8e9355 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 21:01:14 +0000 Subject: [PATCH 1/3] Fix chat hanging on "Connecting to OpenCode..." when process exits The handleAsk promise in chatParticipant only listened for "done" and "error" events from bridge.onEvent. When the OpenCode process exited cleanly (code 0 or null), handleExit only called setState("stopped") without firing any event, so the promise never resolved and the UI hung indefinitely. Two fixes: - opencodeBridge: fire an error event in handleExit for clean exits so listeners are always notified when the process ends - chatParticipant: add an onStateChanged listener as a safety net so the promise resolves even if a state change fires without an event https://claude.ai/code/session_01H4amgmKjeCoRweW1fK9PDc --- src/chat/chatParticipant.ts | 10 +++++ src/chat/opencodeBridge.test.ts | 65 +++++++++++++++++++++++++++++++++ src/chat/opencodeBridge.ts | 4 ++ 3 files changed, 79 insertions(+) diff --git a/src/chat/chatParticipant.ts b/src/chat/chatParticipant.ts index fca49c2..18fb064 100644 --- a/src/chat/chatParticipant.ts +++ b/src/chat/chatParticipant.ts @@ -199,6 +199,7 @@ async function handleAsk( } completed = true; eventListener.dispose(); + stateListener.dispose(); cancelListener.dispose(); // Render subagent summary. @@ -223,6 +224,15 @@ async function handleAsk( } }); + // Safety net: if the bridge stops or errors without emitting an + // event (e.g. process killed externally), resolve the promise so + // the chat response doesn't hang indefinitely. + const stateListener = bridge.onStateChanged((state) => { + if (state === "stopped" || state === "error") { + finish({ error: "OpenCode process exited unexpectedly" }); + } + }); + const cancelListener = token.onCancellationRequested(() => { bridge.cancelCurrentRequest(); stream.markdown("\n_Request cancelled._\n"); diff --git a/src/chat/opencodeBridge.test.ts b/src/chat/opencodeBridge.test.ts index 0b7e075..f9067bb 100644 --- a/src/chat/opencodeBridge.test.ts +++ b/src/chat/opencodeBridge.test.ts @@ -301,6 +301,71 @@ describe("onStateChanged", () => { }); }); +// --------------------------------------------------------------------------- +// process exit — event notification +// --------------------------------------------------------------------------- + +describe("process exit", () => { + function getExitHandler() { + const exitCall = mockProcess.on.mock.calls.find( + ([event]: [string]) => event === "exit" + ); + return exitCall?.[1] as ((code: number | null) => void) | undefined; + } + + it("fires error event on clean exit (code 0)", async () => { + await bridge.start(); + const listener = vi.fn(); + bridge.onEvent(listener); + + const exitHandler = getExitHandler(); + expect(exitHandler).toBeDefined(); + exitHandler!(0); + + expect(listener).toHaveBeenCalledWith( + expect.objectContaining({ + type: "error", + message: expect.stringContaining("exited unexpectedly"), + }) + ); + expect(bridge.state).toBe("stopped"); + }); + + it("fires error event on clean exit (code null)", async () => { + await bridge.start(); + const listener = vi.fn(); + bridge.onEvent(listener); + + const exitHandler = getExitHandler(); + exitHandler!(null); + + expect(listener).toHaveBeenCalledWith( + expect.objectContaining({ + type: "error", + message: expect.stringContaining("exited unexpectedly"), + }) + ); + expect(bridge.state).toBe("stopped"); + }); + + it("fires error event on non-zero exit", async () => { + await bridge.start(); + const listener = vi.fn(); + bridge.onEvent(listener); + + const exitHandler = getExitHandler(); + exitHandler!(1); + + expect(listener).toHaveBeenCalledWith( + expect.objectContaining({ + type: "error", + message: expect.stringContaining("exited with code 1"), + }) + ); + expect(bridge.state).toBe("error"); + }); +}); + // --------------------------------------------------------------------------- // dispose // --------------------------------------------------------------------------- diff --git a/src/chat/opencodeBridge.ts b/src/chat/opencodeBridge.ts index 4beb633..33beb0f 100644 --- a/src/chat/opencodeBridge.ts +++ b/src/chat/opencodeBridge.ts @@ -276,6 +276,10 @@ export class OpenCodeBridge implements vscode.Disposable { }); this.setState("error"); } else { + this._onEvent.fire({ + type: "error", + message: "OpenCode process exited unexpectedly", + }); this.setState("stopped"); } } From 9fc9570e68d5008e84a3522eeac6648441b011c9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 21:02:00 +0000 Subject: [PATCH 2/3] Sync package-lock.json version with package.json https://claude.ai/code/session_01H4amgmKjeCoRweW1fK9PDc --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10abc82..3d09290 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "opencode-for-devcontainers", - "version": "0.1.0", + "version": "0.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "opencode-for-devcontainers", - "version": "0.1.0", + "version": "0.1.2", "license": "MIT", "devDependencies": { "@types/node": "^20.11.0", From 2ede1151cc6ac54fac622dbb1f14687d4bf59912 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Feb 2026 21:09:42 +0000 Subject: [PATCH 3/3] Bump version to 0.1.3 https://claude.ai/code/session_01H4amgmKjeCoRweW1fK9PDc --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3d09290..bd384b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "opencode-for-devcontainers", - "version": "0.1.2", + "version": "0.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "opencode-for-devcontainers", - "version": "0.1.2", + "version": "0.1.3", "license": "MIT", "devDependencies": { "@types/node": "^20.11.0", diff --git a/package.json b/package.json index 22995b6..738c101 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "opencode-for-devcontainers", "displayName": "OpenCode for Dev Containers", "description": "Run OpenCode locally but execute commands within a devcontainer", - "version": "0.1.2", + "version": "0.1.3", "publisher": "marchingphoenix", "license": "MIT", "repository": {