diff --git a/packages/opencode/src/cli/cmd/github.ts b/packages/opencode/src/cli/cmd/github.ts index 672e73d49a9..67d3e551c75 100644 --- a/packages/opencode/src/cli/cmd/github.ts +++ b/packages/opencode/src/cli/cmd/github.ts @@ -481,6 +481,7 @@ export const GithubRunCommand = cmd({ let gitConfig: string let session: { id: string; title: string; version: string } let shareId: string | undefined + let unsubscribeSessionEvents: (() => void) | undefined let exitCode = 0 type PromptFiles = Awaited>["promptFiles"] const triggerCommentId = isCommentEvent @@ -844,6 +845,9 @@ export const GithubRunCommand = cmd({ } function subscribeSessionEvents() { + // Cleanup any existing subscription before creating a new one + unsubscribeSessionEvents?.() + const TOOL: Record = { todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD], todoread: ["Todo", UI.Style.TEXT_WARNING_BOLD], @@ -867,7 +871,7 @@ export const GithubRunCommand = cmd({ } let text = "" - Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => { + const unsubscribe = Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => { if (evt.properties.part.sessionID !== session.id) return //if (evt.properties.part.messageID === messageID) return const part = evt.properties.part @@ -894,6 +898,8 @@ export const GithubRunCommand = cmd({ } } }) + + unsubscribeSessionEvents = unsubscribe } async function summarize(response: string) { diff --git a/packages/opencode/src/provider/models.ts b/packages/opencode/src/provider/models.ts index bae33178467..0d1688bafde 100644 --- a/packages/opencode/src/provider/models.ts +++ b/packages/opencode/src/provider/models.ts @@ -121,12 +121,29 @@ export namespace ModelsDev { } } +let intervalId: ReturnType | undefined +let exitHandlerRegistered = false + +export function startRefreshInterval() { + if (intervalId) return + void ModelsDev.refresh() + intervalId = setInterval(() => ModelsDev.refresh(), 60 * 1000 * 60).unref() + + // Register exit handler only once to prevent multiple registrations + if (!exitHandlerRegistered) { + exitHandlerRegistered = true + process.on("exit", stopRefreshInterval) + } +} + +export function stopRefreshInterval() { + if (intervalId) { + clearInterval(intervalId) + intervalId = undefined + } +} + +// Auto-start the interval on module load (if not disabled) if (!Flag.OPENCODE_DISABLE_MODELS_FETCH && !process.argv.includes("--get-yargs-completions")) { - ModelsDev.refresh() - setInterval( - async () => { - await ModelsDev.refresh() - }, - 60 * 1000 * 60, - ).unref() + startRefreshInterval() } diff --git a/packages/opencode/src/tool/webfetch.ts b/packages/opencode/src/tool/webfetch.ts index a66e66c097b..c64bc57a814 100644 --- a/packages/opencode/src/tool/webfetch.ts +++ b/packages/opencode/src/tool/webfetch.ts @@ -62,7 +62,12 @@ export const WebFetchTool = Tool.define("webfetch", { "Accept-Language": "en-US,en;q=0.9", } - const initial = await fetch(params.url, { signal, headers }) + let initial: Response + try { + initial = await fetch(params.url, { signal, headers }) + } finally { + clearTimeout() + } // Retry with honest UA if blocked by Cloudflare bot detection (TLS fingerprint mismatch) const response = @@ -70,8 +75,6 @@ export const WebFetchTool = Tool.define("webfetch", { ? await fetch(params.url, { signal, headers: { ...headers, "User-Agent": "opencode" } }) : initial - clearTimeout() - if (!response.ok) { throw new Error(`Request failed with status code: ${response.status}`) }