You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 #27557
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/unhandledRejection handlers in opencode.json. oh-my-openagent@4.1.1 and @4.1.2 both reproduce reliably (the offending code is in src/features/background-agent/process-cleanup.ts → registerManagerForCleanup, which calls process.exit(1) inside scheduleForcedExit).
Quit any running opencode processes. Verify with ps -ef | grep -i opencode showing nothing.
Launch OpenCode Desktop.
Wait. Sidecar exits silently with code: 1 somewhere 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 opencode CLI session: opencode .
Launch OpenCode Desktop.
Both processes write to the same SQLite database at ~/.local/share/opencode/opencode.db. Node's experimental node:sqlite doesn't safely handle concurrent writers from independent processes.
After 5–9 minutes the Desktop sidecar exits silently with code: 1 again — 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: oh-my-openagent@4.1.1 (also reproduces on @4.1.2)
functionscheduleForcedExit(cleanupResult,exitCode,exitAfterCleanup=false){if(!_scheduleForcedExitEnabled)returnprocess.exitCode=exitCodeconstexitTimeout=setTimeout(()=>process.exit(),6000)voidPromise.resolve(cleanupResult).finally(()=>{clearTimeout(exitTimeout)if(exitAfterCleanup){process.exit(exitCode)// <-- the silent code-1 exit you see in main.log}})}functionregisterErrorEvent(signal,handler){constlistener=(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)returnlistener}exportfunctionregisterManagerForCleanup(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/unhandledRejection handlers 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 to process.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.lock at 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.db vs opencode-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 oh-my-openagent@4.1.1 (or @4.1.2) to the plugin array in opencode.json.
Wait. Sidecar dies silently with code: 1 between 30 sec and 10 min.
Confirm via ~/Library/Logs/@opencode-ai/desktop/main.log (single warn line) and tail $TMPDIR/oh-my-opencode.log (you'll see the actual error and shutdown trace there).
SQLite contention failure mode: only run one opencode server process per user account at a time. Quit opencode CLI sessions before launching Desktop, and vice versa. Delete *.broken files in ~/.local/share/opencode/ after confirming opencode.db integrity with sqlite3 ~/.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.
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 Nodeprocess.on('uncaughtException', …)handler that catches ANY unhandled rejection in the sidecar — including ones thrown by opencode core itself — and unconditionally callsprocess.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
uncaughtException/unhandledRejectionhandlers inopencode.json.oh-my-openagent@4.1.1and@4.1.2both reproduce reliably (the offending code is insrc/features/background-agent/process-cleanup.ts → registerManagerForCleanup, which callsprocess.exit(1)insidescheduleForcedExit).opencodeprocesses. Verify withps -ef | grep -i opencodeshowing nothing.code: 1somewhere between 30 sec and 10 min later. Nothing in the Desktop log explains why.100% reproduction rate. Removing
oh-my-openagentfrom"plugin"inopencode.jsonmakes 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: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:
opencodeCLI session:opencode .~/.local/share/opencode/opencode.db. Node's experimentalnode:sqlitedoesn't safely handle concurrent writers from independent processes.code: 1again — same observable symptom, totally different cause.Diagnostic signature:
log show --predicate 'eventMessage CONTAINS "192644 of"' --last 60m | wc -lreturns thousands ofSQLITE_MISUSEerrors fromlibsqlite3.dylib. opencode has previously detected this state and renamed the corrupted DB files to*.brokenin~/.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
node:sqlite(warning visible on every sidecar startup)oh-my-openagent@4.1.1(also reproduces on@4.1.2)Evidence
1. Isolation test confirms the offending plugin
plugin: ["cc-safety-net", "oh-my-openagent@v4.1.1"]+ memory MCP + playwright MCPplaywrightMCP disabledplugin: ["cc-safety-net"](oh-my-openagent removed) + memory MCP + playwright MCP enabledRemoving
oh-my-openagentfrom the plugin list is the variable that fixed it. MCP server choice was irrelevant.2. The
process.exit(1)call in the pluginFrom
src/features/background-agent/process-cleanup.tsat v4.1.2 (still present, NOT fixed by v4.1.2):Once any
CleanupManageris constructed (which happens at plugin load), opencode's process gets a globaluncaughtException/unhandledRejectionhandler that always callsprocess.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
runningboarddshows ~9 child node processes terminating in lockstep with the sidecarAt the exact millisecond the sidecar (PID 49810) exited at 19:08:02, 9 child
nodeprocesses terminated viaproc_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
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:
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.[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.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
opencodeCLI server runs concurrently with the Desktop sidecar under the same user account, both processes write to~/.local/share/opencode/opencode.dband Node's experimentalnode:sqliteaccumulates ~1SQLITE_MISUSEerror per second fromlibsqlite3.dylibuntil 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:
~/.local/share/opencode/server.lockat startup; refuse to start (or auto-connect to the existing server) if the lock is held.opencode-desktop.dbvsopencode-cli.db).Updated reproduction steps (no CLI server needed, current behavior)
oh-my-openagent@4.1.1(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 OpenCodecode: 1between 30 sec and 10 min.~/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
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.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
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.