Repository navigation
Description
Summary
OpenCode Desktop's sidecar process exits silently with code 1 anywhere from 30 seconds to ~10 minutes after server ready, with no stderr written to ~/Library/Logs/@opencode-ai/desktop/main.log. The Desktop UI then displays "server is dead". Repeats on every relaunch.
Root cause traced: a third-party opencode plugin (oh-my-openagent) installs a global Node process.on('uncaughtException', …) handler that catches ANY unhandled rejection in the sidecar — including ones thrown by opencode core itself — and unconditionally calls process.exit(1). The plugin's maintainer has acknowledged this in code-yeongyu/oh-my-openagent#3856 (open, with proposed fixes) and the v4.1.1 regression specifically in code-yeongyu/oh-my-openagent#3997 (open).
I'm filing this against opencode anyway because the architecture that lets a single plugin's global error handler take down the entire sidecar with zero diagnostic surface is itself the bug to fix in opencode. Even after the upstream plugin fixes #3856, any future plugin can do the same thing and reproduce this exact failure.
A secondary, independent failure mode (concurrent SQLite access from a co-running CLI server) is documented further down — same observable symptom, different root cause, both worth fixing.
Reproducing
Reproduction A — silent crash from third-party plugin's global error handler
- Configure an opencode plugin that registers global
uncaughtException/unhandledRejectionhandlers inopencode.json.[email protected]and@4.1.2both reproduce reliably (the offending code is insrc/features/background-agent/process-cleanup.ts → registerManagerForCleanup, which callsprocess.exit(1)insidescheduleForcedExit). - Quit any running
opencodeprocesses. Verify withps -ef | grep -i opencodeshowing nothing. - Launch OpenCode Desktop.
- Wait. Sidecar exits silently with
code: 1somewhere between 30 sec and 10 min later. Nothing in the Desktop log explains why.
100% reproduction rate. Removing oh-my-openagent from "plugin" in opencode.json makes the sidecar stay alive indefinitely — confirmed by my isolation testing today (table below).
The actual chain of events for one specific crash today, recovered from oh-my-openagent's log file at $TMPDIR/oh-my-opencode.log:
[2026-05-14T12:44:51.253Z] [auto-compact] session.error received {
"sessionID": "ses_…",
"error": {
"name": "UnknownError",
"data": {
"message": "DrizzleError: Failed to run the query 'begin immediate'
at NodeSQLiteSession.run (.../app.asar/out/main/chunks/node-DbvQoYZ8.js:74693:15)
at SessionProcessor.cleanup (.../node-DbvQoYZ8.js:285666:68)
at SessionProcessor.process (.../node-DbvQoYZ8.js:321876:42)
...
[cause]: Error: file is not a database
at NodeSQLitePreparedQuery.run (.../node-DbvQoYZ8.js:74814:19)"
}
}
}
So opencode's own session processor hit a Drizzle "file is not a database" error (likely cascade from the secondary SQLite-contention failure mode below). The error became an unhandled rejection in opencode core. The plugin's global handler caught it and called process.exit(1) — silent death.
Reproduction B — silent crash from concurrent SQLite access
Independent failure mode that overlaps in symptoms but has a different root cause:
- Open a terminal and start an
opencodeCLI session:opencode . - Launch OpenCode Desktop.
- Both processes write to the same SQLite database at
~/.local/share/opencode/opencode.db. Node's experimentalnode:sqlitedoesn't safely handle concurrent writers from independent processes. - After 5–9 minutes the Desktop sidecar exits silently with
code: 1again — same observable symptom, totally different cause.
Diagnostic signature: log show --predicate 'eventMessage CONTAINS "192644 of"' --last 60m | wc -l returns thousands of SQLITE_MISUSE errors from libsqlite3.dylib. opencode has previously detected this state and renamed the corrupted DB files to *.broken in ~/.local/share/opencode/.
This Drizzle "file is not a database" error (above) almost certainly comes from a previous co-running session that left the WAL in a bad state — the Drizzle layer hits the corruption, throws, and (because of the plugin's handler) the sidecar dies silently.
Environment
- OpenCode Desktop: v1.14.50 (also reproduced on v1.14.48)
- opencode CLI: v1.14.50
- macOS: 26.4.1 (Build 25E253)
- Architecture: Apple Silicon (arm64, Darwin 25.4.0)
- Node SQLite: experimental
node:sqlite(warning visible on every sidecar startup) - Triggering plugin:
[email protected](also reproduces on@4.1.2)
Evidence
1. Isolation test confirms the offending plugin
| Config tested today | Sidecar uptime | Result |
|---|---|---|
plugin: ["cc-safety-net", "[email protected]"] + memory MCP + playwright MCP |
30s – 9 min | dies code 1 every time |
Same plugins, playwright MCP disabled |
1m 49s | still dies code 1 |
plugin: ["cc-safety-net"] (oh-my-openagent removed) + memory MCP + playwright MCP enabled |
>>15 min and still alive | sidecar STABLE |
Removing oh-my-openagent from the plugin list is the variable that fixed it. MCP server choice was irrelevant.
2. The process.exit(1) call in the plugin
From src/features/background-agent/process-cleanup.ts at v4.1.2 (still present, NOT fixed by v4.1.2):
function scheduleForcedExit(cleanupResult, exitCode, exitAfterCleanup = false) {
if (!_scheduleForcedExitEnabled) return
process.exitCode = exitCode
const exitTimeout = setTimeout(() => process.exit(), 6000)
void Promise.resolve(cleanupResult).finally(() => {
clearTimeout(exitTimeout)
if (exitAfterCleanup) {
process.exit(exitCode) // <-- the silent code-1 exit you see in main.log
}
})
}
function registerErrorEvent(signal, handler) {
const listener = (error) => {
process.off(signal, listener)
log(`[background-agent] ${signal} received during shutdown cleanup:`, error)
scheduleForcedExit(handler(error), 1, true) // <-- triggers the exit
}
process.on(signal, listener)
return listener
}
export function registerManagerForCleanup(manager) {
// ... unconditionally registers global handlers on first manager creation:
cleanupErrorHandlers.set("uncaughtException", registerErrorEvent("uncaughtException", cleanupAll))
cleanupErrorHandlers.set("unhandledRejection", registerErrorEvent("unhandledRejection", cleanupAll))
}Once any CleanupManager is constructed (which happens at plugin load), opencode's process gets a global uncaughtException/unhandledRejection handler that always calls process.exit(1). The handler doesn't check whether the error came from the plugin's own code or from somewhere else in opencode — it just exits.
The error log goes to $TMPDIR/oh-my-opencode.log, not opencode's main.log. That's why opencode's main.log shows only [warn] sidecar exited { code: 1 } with no preceding stderr.
3. macOS runningboardd shows ~9 child node processes terminating in lockstep with the sidecar
At the exact millisecond the sidecar (PID 49810) exited at 19:08:02, 9 child node processes terminated via proc_exit:
2026-05-14 19:08:02.050 runningboardd: [anon<node>(501):50483] termination reported by proc_exit
2026-05-14 19:08:02.050 runningboardd: [anon<node>(501):50025] termination reported by proc_exit
2026-05-14 19:08:02.050 runningboardd: [anon<node>(501):50484] termination reported by proc_exit
2026-05-14 19:08:02.050 runningboardd: [anon<node>(501):50044] termination reported by proc_exit
2026-05-14 19:08:02.051 runningboardd: [anon<node>(501):50045] termination reported by proc_exit
2026-05-14 19:08:02.051 runningboardd: [anon<node>(501):50221] termination reported by proc_exit
2026-05-14 19:08:02.052 runningboardd: [anon<node>(501):49903] termination reported by proc_exit
2026-05-14 19:08:02.052 runningboardd: [anon<node>(501):49957] termination reported by proc_exit
2026-05-14 19:08:02.052 runningboardd: [anon<node>(501):49902] termination reported by proc_exit
These are background-agent and MCP-server child processes the plugin and opencode spawned. They die because their parent (the sidecar) called process.exit(1).
4. Sidecar exit-code timeline today
[15:11:30] sidecar exited { code: 1 } uptime ~7 min plugins+MCP, no CLI conflict
[15:17:09] sidecar exited { code: 1 } uptime ~5 min plugins+MCP, no CLI conflict
[15:44:52] sidecar exited { code: 1 } uptime ~8 min plugins+MCP, no CLI conflict
[15:50:00] sidecar exited { code: 0 } manual quit
[15:52:29] sidecar exited { code: 1 } uptime ~2 min plugins+MCP
[16:28:39] sidecar exited { code: 1 } uptime ~9 min plugins+MCP
...
[19:08:02] sidecar exited { code: 1 } uptime 67 sec plugins+MCP, no CLI running
[19:21:47] sidecar exited { code: 1 } uptime ~2 min playwright MCP disabled, oh-my-openagent still active
[19:30:00+] STILL ALIVE oh-my-openagent removed from plugin list
Why this is an opencode bug, not just a plugin bug
The third-party plugin is the proximate trigger, and a fix at the plugin level (#3856) would resolve my immediate problem. But the design that lets a plugin install global error handlers that exit the host process — with zero diagnostic signal — is the deeper failure to fix in opencode itself:
- Plugins should not be able to install global
uncaughtException/unhandledRejectionhandlers that take down the host. Either run plugins in worker threads / vm contexts where their handlers don't affect the main process, or detect/refuse global handler registration from plugins, or document this as forbidden and audit the ecosystem. Today opencode's plugin loader hands the plugin direct access toprocess.on(…)and there's no protection. - The
[warn] sidecar exited { code: 1 }log line should include a reason. Capture stderr deltas, the last hook invoked, the last MCP server activity, the last plugin-emitted log line — anything. Right now there is exactly one log line for what is, to the user, a hard crash. - The Desktop UI's "server is dead" should be replaceable with a dialog like "Sidecar crashed (Plugin X registered process.exit). Restart? Disable plugin X? View log?" Letting the user click "disable" gets them back to working state without needing to grep main.log + edit JSON + relaunch.
- Consider an MCP-server / plugin watchdog: respawn dead plugin children with backoff and a circuit breaker. Other MCP clients (Claude Desktop, Cursor) already do this.
v1.14.49's "Show clearer wrapped server errors in the app" is moving in this direction but doesn't cover the plugin-uncaughtException → sidecar-exit-1 path.
Secondary issue: SQLITE_MISUSE under concurrent access
When an opencode CLI server runs concurrently with the Desktop sidecar under the same user account, both processes write to ~/.local/share/opencode/opencode.db and Node's experimental node:sqlite accumulates ~1 SQLITE_MISUSE error per second from libsqlite3.dylib until one process's SQLite handle becomes unusable. This is a separate failure mode that produces the same observable symptom (silent code-1 exit) and creates the conditions for the Drizzle "file is not a database" cascade above.
Suggested fixes:
- Single-server-per-user enforcement: take an advisory lock on
~/.local/share/opencode/server.lockat startup; refuse to start (or auto-connect to the existing server) if the lock is held. - Per-process DB isolation as fallback: per-PID or per-mode DB path (
opencode-desktop.dbvsopencode-cli.db). - Surface SQLITE_MISUSE to stderr: install a libsqlite3 error log callback so users see something before silent exit.
Updated reproduction steps (no CLI server needed, current behavior)
- Add
[email protected](or@4.1.2) to thepluginarray inopencode.json. killall opencode opencode-cli OpenCode 'OpenCode Helper' 2>/dev/null; verifyps -ef | grep -i opencodeis empty.open -a OpenCode- Wait. Sidecar dies silently with
code: 1between 30 sec and 10 min. - Confirm via
~/Library/Logs/@opencode-ai/desktop/main.log(single warn line) andtail $TMPDIR/oh-my-opencode.log(you'll see the actual error and shutdown trace there).
Workarounds while this is being fixed
- Plugin failure mode: review your
pluginlist inopencode.json. Removeoh-my-openagent(or any plugin that installs globaluncaughtExceptionhandlers — see race condition when applying changes immediatly after each other on same file #3856). Track the upstream fix at code-yeongyu/oh-my-openagent#3856 and #3997. - SQLite contention failure mode: only run one opencode server process per user account at a time. Quit
opencodeCLI sessions before launching Desktop, and vice versa. Delete*.brokenfiles in~/.local/share/opencode/after confirmingopencode.dbintegrity withsqlite3 ~/.local/share/opencode/opencode.db "PRAGMA integrity_check;".
Cross-referenced upstream issues
- code-yeongyu/oh-my-openagent#3856 — "Make global uncaughtException/unhandledRejection handlers opt-in to background-agent feature". Open. Has the same diagnosis and proposes fixes at the plugin level.
- code-yeongyu/oh-my-openagent#3997 — "Sidecar crashes (exit code 1) after first request since v4.1.1 update". Open. Filed 1 day ago; matches my repro precisely.
- code-yeongyu/oh-my-openagent#3772 — "OmO creates huge log file due to EPIPE error". Open. Different symptom of the same underlying handler design.
opencode v1.14.41 release notes: "Moved the desktop app's local server into a separate utility process for more reliable startup and shutdown" — this isolation is what makes the silent crash possible without taking the whole Desktop process down.
opencode v1.14.49 bugfix: "Show clearer wrapped server errors in the app" — moves in the right direction; doesn't cover the plugin-crash path observed here.
Activity
github-actions commentedon May 14, 2026
This issue might be a duplicate of existing issues. Please check:
opencode runsessions crash withSQLITE_BUSYdue tobusy_timeout=0— same shared SQLite contention root causeSQLiteError: bad parameter or other API misuse(SQLITE_MISUSE) — same error code as observed in your issueThese all stem from the same structural issue: multiple opencode server processes sharing a single SQLite database without process-level write coordination.
ottosulin commentedon May 14, 2026
Update: SQLite contention is NOT the only cause — also reproduces with no other opencode process running
After filing this issue I tested again under different conditions and found this also reproduces with zero other
opencodeprocesses running on the system. So my SQLite-contention root cause is incomplete — it's a real symptom and a real failure mode, but it's not the only one.New repro (today, 2026-05-14 19:06:51)
Killed all
opencodeprocesses (verified withps -ef | grep opencodereturning nothing). Launched OpenCode Desktop fresh. Sidecar started cleanly, server ready athttp://127.0.0.1:63572. 67 seconds later the sidecar exited withcode: 1. Same silent death pattern, no stderr.log show --predicate 'eventMessage CONTAINS "192644 of"' --start "2026-05-14 19:00:00" --end "2026-05-14 19:09:00" | wc -lreturns 0 for that window. No SQLITE_MISUSE this time.What's likely actually happening
runningboarddshows that at the EXACT millisecond of sidecar death, 9 childnodeprocesses also terminated viaproc_exit:All 9 are
/Users/ottosulin/.nvm/versions/node/v20.19.2/bin/node— these are MCP server children spawned by the sidecar. Myopencode.jsonconfigures these MCP servers (relevant entries):The first 7 MCP node children spawned 3–5 seconds after sidecar start. The last 2 (PIDs 50483, 50484) spawned at
19:07:50.448and19:07:50.454— 12 seconds before sidecar death. This timing strongly suggests an MCP-server-side error propagated up and crashed the sidecar.Suspect:
@playwright/mcp@latest --extensionmodeI'm running
@playwright/mcpv0.0.75 (latest as of repro), which ships an alpha playwright-core (1.61.0-alpha-1778188671000). Looking at upstream playwright-mcp release notes, the--extensioncode path has been actively bug-fixed in the last three releases:--isolatedmode", "Forward browser-level CDP commands in extension mode" — directly touches--extensionbrowser_run_code_unsafeinstead of crashing the server" — direct admission that previous versions crashed the MCP server on unhandled rejection--extensionfixes around channel/executablePath resolutionI'll be testing whether disabling
playwright(and/ormemory) in my opencode.json prevents the silent sidecar death. Will report back.Implications for opencode
Even if the upstream MCP server is the proximate trigger, the sidecar dying silently with no stderr and no UI surface is the bug to fix in opencode itself:
~/Library/Logs/@opencode-ai/desktop/main.log), surface "MCP server X disconnected" in the Desktop UI, and continue serving the user with the remaining MCP servers + built-in tools. v1.14.49's "Show clearer wrapped server errors in the app" is moving in this direction but evidently doesn't cover this code path.[warn] sidecar exited { code: 1 }line in main.log should include WHY the sidecar exited. Capture the last N stderr lines, the last MCP server activity, the last tool call — anything. Right now there is literally one log line and zero context.Updated reproduction steps (no CLI server needed)
opencode.json(the more, the faster repro). Especially@playwright/mcp@latest --extensionif you have Chrome installed.killall opencode opencode-cli OpenCode 'OpenCode Helper' 2>/dev/null; verifyps -ef | grep -i opencodeis empty.open -a OpenCodecode: 1somewhere between 30 seconds and 10 minutes. No stderr. No crash report.~/Library/Logs/@opencode-ai/desktop/main.logandlog show --predicate 'eventMessage CONTAINS[c] "termination reported by proc_exit"' --predicate 'eventMessage CONTAINS[c] "anon<node>"' --last 10m | head— you'll see N child node processes terminating in lock-step with the sidecar at the moment of death.Apologies for the partial diagnosis in the original post
The SQLite-contention story is a real second failure mode (still reproducible if you have a CLI server running concurrently), but it's not the primary cause. The primary cause looks like MCP-server failure cascading into sidecar death. Both behaviors should be fixed:
~/.local/share/opencode/opencode.dbbetween processes without locking (the SQLite issue from the original post).I'll add another comment once I've confirmed which MCP server is the trigger by selectively disabling them.
[-]Desktop sidecar exits silently with code 1 after 5–9 min when an `opencode` CLI server runs concurrently (shared SQLite DB)[/-][+]Desktop sidecar exits silently with code 1 — caused by plugin crash (most cases) and concurrent SQLite access (secondary)[/+][-]Desktop sidecar exits silently with code 1 — caused by plugin crash (most cases) and concurrent SQLite access (secondary)[/-][+]Desktop sidecar exits silently with code 1 — third-party plugin's global uncaughtException handler calls process.exit(1) on any unhandled rejection in the sidecar[/+]github-actions commentedon Jul 14, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.