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
opencode's Zed editor-context fallback (introduced in #24352, refined in #24656) opens Zed's SQLite database on the TUI's main thread and polls it at ~1 Hz. When Zed is running concurrently, Zed holds the WAL lock and opencode's read enters SQLite's busy_handler exponential-backoff loop. Because that loop runs on the same thread that does epoll_wait on stdin, keystrokes are not read for the duration of the backoff (~5 s with default busy_timeout). After the timeout, the subsequent pread64() on the WAL file faults pages in from disk → folio_wait_bit_common → another 1-4 s of D-state. End-to-end, the TUI becomes unresponsive in bursts of 5-10 s, intermittently, whenever Zed happens to be active.
This affects any user who runs opencode in a terminal while Zed is open in the same workspace (a very common setup, since the feature exists specifically to bridge them).
Versions
opencode 1.15.4
Zed 1.1.8
Linux 6.8.0 (Ubuntu 24.04.4) — Zed's DB lives on a bind-mounted host filesystem; same behavior reproduces on a plain ext4 mount
bun (embedded in opencode bundle)
Reproduction
Open Zed on a workspace and edit a file (so Zed is regularly writing WAL).
In any terminal, launch opencode in the same or a sibling project.
Type continuously. Within minutes, observe sporadic 5-10 s freezes where keystrokes stop being echoed/processed.
Reproduces 100% on Linux when both are open and Zed is actively writing.
Evidence
strace -p <opencode-main-tid> -tt -T -y excerpt during a freeze:
The 175ms → 180 → 185 → 191 sequence is SQLite's busy_handler exponential backoff against a contended WAL lock — happening on opencode's main thread, which is the same thread blocked on epoll_wait(stdin). Over 34 s of trace, that thread made 22,352 syscalls against db.sqlite-wal.
Concurrent per-200ms thread-state sampling caught the main thread (TID 5415) in uninterruptible disk wait for 4.2 s straight while waiting for the page-cache fault:
17:51:46.326 → 17:51:50.537 TID 5415 state=D wchan=folio_wait_bit_common
Both stalls — the busy-wait sleep loop and the D-state read — happen on the input thread, which is why keystrokes are dropped.
…called on every poll tick. The query path opens the live Zed db (no ?mode=ro or PRAGMA query_only), which means Zed's exclusive WAL lock blocks it.
Workaround
Setting OPENCODE_ZED_DB to a path of a quiet, empty SQLite file (or any file that isn't a real db) sidesteps the bug — the query throws no such table, opencode's try/catch returns {type:"unavailable"}, no contention. The Zed integration is effectively disabled. There's currently no documented config-file or CLI flag to disable this feature; the env var is the only knob.
Suggested fixes (any one would resolve it)
Open the db with mode=ro&immutable=1 (or the bun:sqlite equivalent). Zed-side WAL writes won't contend with an immutable reader. Plus a short busy_timeout (e.g. 50 ms) so even unexpected contention can't stall the input thread.
Move the poll off the main fiber. Run it in a worker; post the result back via a channel. The TUI's input loop must never wait on file I/O.
Skip the SQLite fallback when the primary websocket bridge is connected. PR feat(tui): read Zed editor context from state db #24352 describes the SQLite path as a fallback for when the websocket is unavailable, but the strace shows the poll runs continuously regardless. A "websocket connected → don't poll SQLite" gate would eliminate the issue for the common case (Zed running with the websocket bridge).
Add a documented opt-out. A config key like experimental.zedEditorContext: false would let users disable this without relying on the undocumented OPENCODE_ZED_DB env var.
Happy to test any candidate fix on the reproducer.
Plugins
None
OpenCode version
1.15.4
Steps to reproduce
Prerequisites
Linux (kernel ≥ 5.x). Should also reproduce on macOS via the Library/Application Support/Zed/... path, but I've only verified Linux.
opencode ≥ 1.15 installed.
Zed installed (any recent version; tested with 1.1.8).
strace, awk, grep for objective verification.
Steps
Start Zed and start writing.
zed ~/somedir & # open any directory in Zed
In Zed, open a file and begin editing — keep typing/saving so Zed is actively writing to its WAL.
In a separate terminal, launch opencode in any project and start an interaction.
cd ~/some-other-project
opencode
Then type a prompt and press enter (so opencode has an active session that wants editor context).
Confirm opencode has opened Zed's db.
OC_PID=$(pgrep -f "opencode/bin/opencode" | head -1)
ls -l /proc/$OC_PID/fd 2>/dev/null | grep -i 'zed/db.*sqlite'
Expected: at least one fd pointing at ~/.local/share/zed/db/0-stable/db.sqlite (or the macOS path).
Watch the main thread state for D-state (uninterruptible disk wait) and SQLite busy-loop sleeps.
In a third terminal, run this sampler — it samples opencode's main thread state every 200 ms:
OC_PID=$(pgrep -f "opencode/bin/opencode" | head -1)
while kill -0 "$OC_PID" 2>/dev/null; do
ts=$(date +%H:%M:%S.%3N)
state=$(awk '{s=$0; sub(/.*) /,"",s); print substr(s,1,1)}' /proc/$OC_PID/stat 2>/dev/null)
wchan=$(cat /proc/$OC_PID/wchan 2>/dev/null)
printf '%s %s %s\n' "$ts" "$state" "$wchan"
sleep 0.2
done | tee /tmp/oc-sampler.log
In opencode, keep typing. Within a few minutes of normal use you should see runs of consecutive samples where the main thread is in one of:
D folio_wait_bit_common — kernel waiting for file pages (the pread64 on the WAL).
S hrtimer_nanosleep — SQLite's busy_handler exponential backoff.
The freeze visible to the user (keystrokes stop being processed) lines up exactly with these runs. Single freeze events typically span 5-10 s.
(Optional) Strace the main thread to see the syscalls directly.
sudo strace -p $OC_PID -tt -T -y -s 200 -e trace=pread64,clock_nanosleep,nanosleep
-o /tmp/oc.strace
Run for ~60 s, then:
The SQLite busy-handler backoff pattern (growing sleeps in the 100-200ms range):
grep clock_nanosleep /tmp/oc.strace | grep -oE 'tv_nsec=[0-9]+' | head -20
Expected: many thousands of reads against db.sqlite-wal over a minute, and a series of clock_nanosleep calls whose tv_nsec grows geometrically (e.g. 175000000 → 180000000 → 185000000 →
191000000 → …) — SQLite's default busy-handler.
Workaround that confirms the diagnosis
Restart opencode with the env var pointed at an empty file:
Re-run the sampler. With this in place: no D folio_wait_bit_common, no hrtimer_nanosleep runs, no perceptible freezes — even with Zed actively writing in the background. This isolates
the cause to the Zed-db poll specifically.
Description
Summary
opencode's Zed editor-context fallback (introduced in #24352, refined in #24656) opens Zed's SQLite database on the TUI's main thread and polls it at ~1 Hz. When Zed is running concurrently, Zed holds the WAL lock and opencode's read enters SQLite's
busy_handlerexponential-backoff loop. Because that loop runs on the same thread that doesepoll_waiton stdin, keystrokes are not read for the duration of the backoff (~5 s with defaultbusy_timeout). After the timeout, the subsequentpread64()on the WAL file faults pages in from disk →folio_wait_bit_common→ another 1-4 s of D-state. End-to-end, the TUI becomes unresponsive in bursts of 5-10 s, intermittently, whenever Zed happens to be active.This affects any user who runs
opencodein a terminal while Zed is open in the same workspace (a very common setup, since the feature exists specifically to bridge them).Versions
1.15.41.1.8Reproduction
opencodein the same or a sibling project.Reproduces 100% on Linux when both are open and Zed is actively writing.
Evidence
strace -p <opencode-main-tid> -tt -T -yexcerpt during a freeze:The
175ms → 180 → 185 → 191sequence is SQLite'sbusy_handlerexponential backoff against a contended WAL lock — happening on opencode's main thread, which is the same thread blocked onepoll_wait(stdin). Over 34 s of trace, that thread made 22,352 syscalls againstdb.sqlite-wal.Concurrent per-200ms thread-state sampling caught the main thread (TID
5415) in uninterruptible disk wait for 4.2 s straight while waiting for the page-cache fault:Both stalls — the busy-wait sleep loop and the D-state read — happen on the input thread, which is why keystrokes are dropped.
Root cause
The bundled code at
packages/opencode/src/cli/cmd/tui/context/editor-zed.tsopens Zed's SQLite db withbun:sqliteand runs queries synchronously from what appears to be the TUI's main fiber. Bundled excerpt:…called on every poll tick. The query path opens the live Zed db (no
?mode=roorPRAGMA query_only), which means Zed's exclusive WAL lock blocks it.Workaround
Setting
OPENCODE_ZED_DBto a path of a quiet, empty SQLite file (or any file that isn't a real db) sidesteps the bug — the query throwsno such table, opencode's try/catch returns{type:"unavailable"}, no contention. The Zed integration is effectively disabled. There's currently no documented config-file or CLI flag to disable this feature; the env var is the only knob.Suggested fixes (any one would resolve it)
mode=ro&immutable=1(or the bun:sqlite equivalent). Zed-side WAL writes won't contend with an immutable reader. Plus a shortbusy_timeout(e.g. 50 ms) so even unexpected contention can't stall the input thread.experimental.zedEditorContext: falsewould let users disable this without relying on the undocumentedOPENCODE_ZED_DBenv var.Happy to test any candidate fix on the reproducer.
Plugins
None
OpenCode version
1.15.4
Steps to reproduce
Prerequisites
Steps
zed ~/somedir & # open any directory in Zed
In Zed, open a file and begin editing — keep typing/saving so Zed is actively writing to its WAL.
cd ~/some-other-project
opencode
Then type a prompt and press enter (so opencode has an active session that wants editor context).
OC_PID=$(pgrep -f "opencode/bin/opencode" | head -1)
ls -l /proc/$OC_PID/fd 2>/dev/null | grep -i 'zed/db.*sqlite'
Expected: at least one fd pointing at ~/.local/share/zed/db/0-stable/db.sqlite (or the macOS path).
In a third terminal, run this sampler — it samples opencode's main thread state every 200 ms:
OC_PID=$(pgrep -f "opencode/bin/opencode" | head -1)
while kill -0 "$OC_PID" 2>/dev/null; do
ts=$(date +%H:%M:%S.%3N)
state=$(awk '{s=$0; sub(/.*) /,"",s); print substr(s,1,1)}' /proc/$OC_PID/stat 2>/dev/null)
wchan=$(cat /proc/$OC_PID/wchan 2>/dev/null)
printf '%s %s %s\n' "$ts" "$state" "$wchan"
sleep 0.2
done | tee /tmp/oc-sampler.log
The freeze visible to the user (keystrokes stop being processed) lines up exactly with these runs. Single freeze events typically span 5-10 s.
sudo strace -p $OC_PID -tt -T -y -s 200 -e trace=pread64,clock_nanosleep,nanosleep
-o /tmp/oc.strace
Run for ~60 s, then:
How many syscalls hit Zed's WAL during the trace?
grep -c 'zed/db/0-stable/db.sqlite-wal' /tmp/oc.strace
The SQLite busy-handler backoff pattern (growing sleeps in the 100-200ms range):
grep clock_nanosleep /tmp/oc.strace | grep -oE 'tv_nsec=[0-9]+' | head -20
Expected: many thousands of reads against db.sqlite-wal over a minute, and a series of clock_nanosleep calls whose tv_nsec grows geometrically (e.g. 175000000 → 180000000 → 185000000 →
191000000 → …) — SQLite's default busy-handler.
Workaround that confirms the diagnosis
Restart opencode with the env var pointed at an empty file:
touch
/.opencode-zed-stub.sqlite/.opencode-zed-stub.sqlite opencodeOPENCODE_ZED_DB=
Re-run the sampler. With this in place: no D folio_wait_bit_common, no hrtimer_nanosleep runs, no perceptible freezes — even with Zed actively writing in the background. This isolates
the cause to the Zed-db poll specifically.
Screenshot and/or share link
No response
Operating System
Ubuntu 24.04 LTS
Terminal
Superset / Tilix