From d554e7aaefba37bb62c7fb4711a52841f8003a8a Mon Sep 17 00:00:00 2001 From: Ian Maurer Date: Tue, 11 Nov 2025 17:13:55 -0500 Subject: [PATCH] fix(cli): always dispose instance on error to prevent hanging; add friendly ProviderModelNotFoundError/InitError messages\n\n- Wrap bootstrap callback in try/finally to guarantee Instance.dispose()\n- Format provider/model errors into actionable guidance (opencode models, config)\n\nRepro: running `opencode run --model typo/claude-haiku-4-5` prints stack and hangs until SIGINT due to lingering watchers.\nFix: disposing Instance tears down watchers/subscriptions, allowing process to exit.\n\nNotes: Prior attempt (#3083) explicitly exited; this approach addresses root cause without forcing exit and improves UX for common misconfigurations. --- packages/opencode/src/cli/bootstrap.ts | 10 +++++++--- packages/opencode/src/cli/error.ts | 12 ++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/opencode/src/cli/bootstrap.ts b/packages/opencode/src/cli/bootstrap.ts index 2114cbc565..d5b646dd5c 100644 --- a/packages/opencode/src/cli/bootstrap.ts +++ b/packages/opencode/src/cli/bootstrap.ts @@ -6,9 +6,13 @@ export async function bootstrap(directory: string, cb: () => Promise) { directory, init: InstanceBootstrap, fn: async () => { - const result = await cb() - await Instance.dispose() - return result + // Ensure we always dispose instance state, even on errors, + // so the CLI does not hang due to lingering watchers/subscriptions. + try { + return await cb() + } finally { + await Instance.dispose() + } }, }) } diff --git a/packages/opencode/src/cli/error.ts b/packages/opencode/src/cli/error.ts index 1bc20de323..7a8f40bfe5 100644 --- a/packages/opencode/src/cli/error.ts +++ b/packages/opencode/src/cli/error.ts @@ -1,10 +1,22 @@ import { Config } from "../config/config" import { MCP } from "../mcp" +import { Provider } from "../provider/provider" import { UI } from "./ui" export function FormatError(input: unknown) { if (MCP.Failed.isInstance(input)) return `MCP server "${input.data.name}" failed. Note, opencode does not support MCP authentication yet.` + if (Provider.ModelNotFoundError.isInstance(input)) { + const { providerID, modelID } = input.data + return [ + `Model not found: ${providerID}/${modelID}`, + `Try: \`opencode models\` to list available models`, + `Or check your config (opencode.json) provider/model names`, + ].join("\n") + } + if (Provider.InitError.isInstance(input)) { + return `Failed to initialize provider "${input.data.providerID}". Check credentials and configuration.` + } if (Config.JsonError.isInstance(input)) { return ( `Config file at ${input.data.path} is not valid JSON(C)` + (input.data.message ? `: ${input.data.message}` : "")