Description
OpenCode Backend Bug: Tool Execution Hangs and Event Loop Blockage
Bug Description
The OpenCode backend experiences two severe stability issues when executing the bash tool:
- Indefinite Hangs (Ghost Processes): When a fast-exiting command (like
jq, echo, or commands that crash instantly) is executed, the session sometimes hangs permanently. The tool remains in the running state in the UI indefinitely, even though the underlying OS process has already terminated.
- Event Loop Blockage (
database is locked): When a command emits thousands of lines of output very quickly (e.g., npm install, a large grep, or cat on a massive file), the backend Node.js process chokes, leading to SQLiteError: database is locked exceptions. This drops events and causes the session to hit the 15-second heartbeat timeout.
Root Causes
- Race Condition in
CrossSpawnSpawner: CrossSpawnSpawner relies strictly on the spawn event to resolve the Effect promise. If a process exits so fast that the spawn event is omitted or misordered by the OS/Node bindings, the resume() callback is never called, leaving the execution promise blocked forever.
- Synchronous Metadata Writes: The stream processor (
Stream.runForEach) invokes ctx.metadata(...) for every single chunk emitted by the process stdout/stderr. This queues thousands of synchronous db.insert().run() calls on the main thread, locking the database and completely starving the event loop. Furthermore, when the process does exit, the background stream fiber is instantly interrupted, which silently drops any output chunks still buffered in memory.
Steps to Reproduce
Issue 1: Indefinite Hangs
- Dispatch multiple fast-exiting tools at once, or run a single incredibly fast tool like
jq '{test: 1}'.
- Notice that randomly, the UI gets stuck on "running command".
- Check
ps aux | grep jq — the process is dead, but the backend is still waiting.
Issue 2: DB Locking and Event Loop Starvation
- Run a command that outputs massive amounts of text instantly, e.g.,
cat /dev/urandom | head -n 100000 | base64 or find /.
- Watch the logs fill with unhandled
SQLiteError: database is locked.
- The UI will eventually stall and the session will crash or timeout.
Expected Behavior
- Fast-exiting processes should reliably resolve their execution promise regardless of event ordering.
- High-volume output commands should throttle their metadata updates (e.g., every 250ms) to avoid locking the single-writer SQLite database and choking the Node event loop.
- The stream fiber should fully drain its buffered output before completing the tool execution, ensuring no trailing output is truncated.
OpenCode version
1.3.13
Description
OpenCode Backend Bug: Tool Execution Hangs and Event Loop Blockage
Bug Description
The OpenCode backend experiences two severe stability issues when executing the
bashtool:jq,echo, or commands that crash instantly) is executed, the session sometimes hangs permanently. The tool remains in therunningstate in the UI indefinitely, even though the underlying OS process has already terminated.database is locked): When a command emits thousands of lines of output very quickly (e.g.,npm install, a largegrep, orcaton a massive file), the backend Node.js process chokes, leading toSQLiteError: database is lockedexceptions. This drops events and causes the session to hit the 15-second heartbeat timeout.Root Causes
CrossSpawnSpawner:CrossSpawnSpawnerrelies strictly on thespawnevent to resolve theEffectpromise. If a process exits so fast that thespawnevent is omitted or misordered by the OS/Node bindings, theresume()callback is never called, leaving the execution promise blocked forever.Stream.runForEach) invokesctx.metadata(...)for every single chunk emitted by the process stdout/stderr. This queues thousands of synchronousdb.insert().run()calls on the main thread, locking the database and completely starving the event loop. Furthermore, when the process does exit, the background stream fiber is instantly interrupted, which silently drops any output chunks still buffered in memory.Steps to Reproduce
Issue 1: Indefinite Hangs
jq '{test: 1}'.ps aux | grep jq— the process is dead, but the backend is still waiting.Issue 2: DB Locking and Event Loop Starvation
cat /dev/urandom | head -n 100000 | base64orfind /.SQLiteError: database is locked.Expected Behavior
OpenCode version
1.3.13