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
{{ message }}
Repository navigation
tui: "Restart" after /update exits to the shell instead of restarting OpenCode #56103
After installing an update from the TUI update dialog (the /update command or the update notification), the dialog reads "Update successful! A restart is required. Any active sessions will be resumed automatically." and offers a Restart button. Pressing Restart does not restart OpenCode: the TUI closes and drops back to the shell prompt of the terminal where OpenCode was running. Nothing is relaunched, the session is not resumed, and no message is shown — opencode has to be started manually to get the new version. This happens every time an update is installed this way.
Expected: Restart relaunches the updated OpenCode in the same terminal and resumes the session, as the dialog states. If exiting on purpose is intended (see #47364), the dialog should not promise a restart with automatic session resume and should tell the user to run opencode again.
Plugins
None.
OpenCode version
2.0.31 (observed while updating 2.0.30 → 2.0.31; curl install at ~/.opencode/bin/opencode, channel latest)
Steps to reproduce
Run OpenCode 2.0.30 in a terminal (Ghostty + bash).
When 2.0.31 is published, open the update dialog (notification or /update) and press Update.
The dialog shows "Update successful! A restart is required. Any active sessions will be resumed automatically." with the Skip / Restart buttons.
Press Restart.
The TUI exits to the shell. No new process is spawned; you must run opencode yourself.
Screenshot and/or share link
No response
Operating System
Pop!_OS 24.04 LTS (kernel 7.1.5, x86_64)
Terminal
Ghostty
Additional context
Log evidence from the machine (opencode.log):
message="updated OpenCode" from=2.0.30 to=2.0.31 method=curl at 14:09:36 UTC — after this, the TUI session ends and no process is started by the Restart action.
message="cli starting" version=2.0.31 args=[] at 14:10:37 UTC — the manual relaunch a minute later, which then replaces the version-mismatched background service normally.
Code path: restart() in packages/tui/src/context/update-notification.tsx only calls the TUI exit; the dialog copy is in packages/tui/src/component/dialog-update.tsx. PR #47364 ("Exit normally after an update; the user reopens OpenCode") removed the self-relaunch wrapper, so the button and copy no longer match the behavior.
Thanks for the detailed report and the log lines. I couldn't fully reproduce this in our sandbox. Our test harness runs OpenCode in an isolated environment, so /update stopped at "Could not detect the installation method" and never got to the Restart button.
The code agrees with what you describe, though. In 2.0.30 and on the current dev branch, the Restart action in packages/tui/src/context/update-notification.tsx only exits the TUI and does not start a new process. Meanwhile the dialog in packages/tui/src/component/dialog-update.tsx still says a restart is required and that sessions will be resumed automatically. That matches #47364, which removed the self-relaunch on purpose without changing the dialog text.
This needs a maintainer to check it with a real curl install. Until it's resolved, running opencode again after pressing Restart starts the new version, as you found.
Adding a side-effect that makes this more than a UX annoyance: the unclean exit appears to damage plugin SQLite databases.
Observed on 2 of 3 machines in my fleet (both Silverblue hosts, opencode-mem plugin active) after using this Restart button on 2.0.32 → 2.1.2:
~/.opencode-mem/data/*.db left with 0-byte -wal files and stale -tshm files
PRAGMA integrity_check failures: btree corruption, out-of-order rowids, invalid page numbers in user-prompts.db, user-profiles.db, and project shard DBs
opencode-mem then failed to initialize with I/O error: short read on WAL frame ... expected 4096 bytes, got 0 and Corrupt database: Payload too small for indicated header size
The third machine (HP-Z840) was unaffected, and pre-existing long-running server processes on all three had logged unrelated plugin load warnings around the same window, so this is correlation rather than proof. But the failure mode fits: the Restart path exits without letting SQLite checkpoint/close cleanly, and a hard kill mid-write is exactly how you get truncated WALs.
Salvage was possible (sqlite Connection.backup + row-by-row recovery for the worst files; backups kept), but ideally the exit path should either actually restart as the dialog promises, or at minimum close/checkpoint plugin DBs before quitting.
Happy to provide the full recovery log or the corrupt DB files if useful for debugging.
Confirming this still repros on 2.1.4 (curl install, ~/.opencode/bin/opencode, channel latest) and adding a second occurrence for the DB damage I reported above.
Restart is still exit-only. After the update dialog's Restart press, the TUI closes, the shell prompt returns, nothing relaunches, and no session is resumed. Reproduced twice on two different machines (HP-Z840 and Dell-7740, both Fedora Silverblue). Same failure signature as the original report, so #47364's removal of the self-relaunch is still not reflected in the copy or the button.
On the wording: "reload" and "restart" are not the same operation. If relaunching is intentionally gone, the dialog should say "Shut down — run opencode again to start the new version" and not "A restart is required... any active sessions will be resumed automatically". Right now the UI promises the operation it no longer performs.
DB damage recurrence (2026-10-25, opencode-mem, turso/libsql):
Signature: <db>-wal truncated to 0 bytes next to a stale <db>-tshm (86,016 bytes). Every later open fails with I/O error: short read on WAL frame at offset N: expected 4096 bytes, got 0. Auto-capture retries 3x, then user-profile learning and idle processing abort on the same error.
Timeline on HP-Z840: user-profiles.db last successful write 2026-10-20 22:59, first failure 2026-10-21 00:00:43. Second event left ai-sessions.db, metadata.db and skills.db all stamped 2026-10-22 06:06:14.
New detail vs. my earlier comment: this time the main .db files passed PRAGMA integrity_check — the damage was sidecar-only. The btree/rowid failures I reported earlier were likely a worse instance of the same truncation rather than a separate defect.
One detection wrinkle: an empty -wal beside a -tshm is also the normal shape of an idle DB (turso recreates both on demand), and neither sqlite3 nor python's sqlite3 reproduces turso's own shm validation. So this can only be repaired, not cleanly detected: verify the main DB, delete the sidecars, let turso rebuild. That heals in place, no restart needed, no data loss.
Plugin DBs live in the shared server (opencode serve --service), not in the TUI, so a hard stop takes every plugin database down at the same moment.
Ask: whatever the exit path does, plugins need a chance to close or checkpoint SQLite handles before the process dies — a SIGTERM with a grace period, or an awaited plugin shutdown hook. That converts this from data damage into a clean shutdown regardless of whether Restart ever relaunches.
Happy to run diagnostics against my curl install — the exit path is reproducible on demand here, which is what the earlier reply said was missing for triage.
Follow-up with the host-side half, since the earlier reply said triage needed a real curl install. Measured on 2.1.4 (~/.opencode/bin/opencode, channel latest), Fedora Silverblue.
The restart path, as executed:service restart = St.stop() then St.ensure(). St.stop() runs the PTY handoff, then:
SIGTERM to the opencode serve --service pid;
liveness poll (kill(pid, 0)) every stopPollInterval: 50 ms for stopPollAttempts: 100 — 5 s total;
unconditional SIGKILL, then another liveness wait.
So the dialog's "restart" is: kill the shared server with a 5 s tolerance, then try to start a new one. On this install the start half doesn't happen (TUI exits to the shell), which is the behaviour already reported above.
Why this damages plugin state. Plugins live in the shared server, not the TUI — the same process that gets SIGTERM/SIGKILL. The plugin API declares Hooks.dispose?: () => Promise<void>, but I could not find any call site that invokes a server plugin's dispose on shutdown; every .dispose?.() hit in the bundle is TUI/renderable-side. What does exist is the runtime's own process.on("SIGTERM") → interrupt the main fiber → teardown → process.exit(), which can cut an async plugin cleanup short. A plugin's only reliable chance is therefore its own SIGTERM handler, racing the 5 s budget.
That is the healthy direction — with a pre-close PRAGMA wal_checkpoint(TRUNCATE) in the plugin, the WAL is flushed on the way out and nothing is left half-written. Without it, the same databases have twice been left with a 0-byte -wal beside a stale -tshm, after which every open fails with I/O error: short read on WAL frame at offset N: expected 4096 bytes, got 0 and the whole memory subsystem (tools, capture, web server) is gated off.
Plugin-side fix, if useful as a reference: tickernelz/opencode-mem#440 — checkpoint before database.close(), bounded so it cannot outlast a shutdown window.
Host-side ask (this is the part a plugin can't fix): whichever path tears the server down should either call plugin dispose and await it, or SIGTERM with a grace period longer than the cleanup needs, rather than a fixed 5 s followed by SIGKILL. And separately: if relaunching really is gone per #47364, the button and copy should say "Shut down — run opencode again", because "Restart" with "sessions will be resumed automatically" is currently a promise the code doesn't keep.
Happy to test a diagnostic build — the exit path is reproducible on demand here.
Activity
opencode-agent commentedon Oct 16, 2026
Thanks for the detailed report and the log lines. I couldn't fully reproduce this in our sandbox. Our test harness runs OpenCode in an isolated environment, so
/updatestopped at "Could not detect the installation method" and never got to the Restart button.The code agrees with what you describe, though. In 2.0.30 and on the current
devbranch, the Restart action inpackages/tui/src/context/update-notification.tsxonly exits the TUI and does not start a new process. Meanwhile the dialog inpackages/tui/src/component/dialog-update.tsxstill says a restart is required and that sessions will be resumed automatically. That matches #47364, which removed the self-relaunch on purpose without changing the dialog text.This needs a maintainer to check it with a real curl install. Until it's resolved, running
opencodeagain after pressing Restart starts the new version, as you found.Related (not duplicates):
serveprocess from the previous version keeps running after an auto-updatejkemp814 commentedon Oct 21, 2026
Adding a side-effect that makes this more than a UX annoyance: the unclean exit appears to damage plugin SQLite databases.
Observed on 2 of 3 machines in my fleet (both Silverblue hosts, opencode-mem plugin active) after using this Restart button on 2.0.32 → 2.1.2:
~/.opencode-mem/data/*.dbleft with 0-byte-walfiles and stale-tshmfilesPRAGMA integrity_checkfailures: btree corruption, out-of-order rowids, invalid page numbers inuser-prompts.db,user-profiles.db, and project shard DBsI/O error: short read on WAL frame ... expected 4096 bytes, got 0andCorrupt database: Payload too small for indicated header sizeThe third machine (HP-Z840) was unaffected, and pre-existing long-running server processes on all three had logged unrelated plugin load warnings around the same window, so this is correlation rather than proof. But the failure mode fits: the Restart path exits without letting SQLite checkpoint/close cleanly, and a hard kill mid-write is exactly how you get truncated WALs.
Salvage was possible (sqlite
Connection.backup+ row-by-row recovery for the worst files; backups kept), but ideally the exit path should either actually restart as the dialog promises, or at minimum close/checkpoint plugin DBs before quitting.Happy to provide the full recovery log or the corrupt DB files if useful for debugging.
jkemp814 commentedon Oct 26, 2026
Confirming this still repros on 2.1.4 (curl install,
~/.opencode/bin/opencode, channellatest) and adding a second occurrence for the DB damage I reported above.Restart is still exit-only. After the update dialog's Restart press, the TUI closes, the shell prompt returns, nothing relaunches, and no session is resumed. Reproduced twice on two different machines (HP-Z840 and Dell-7740, both Fedora Silverblue). Same failure signature as the original report, so #47364's removal of the self-relaunch is still not reflected in the copy or the button.
On the wording: "reload" and "restart" are not the same operation. If relaunching is intentionally gone, the dialog should say "Shut down — run
opencodeagain to start the new version" and not "A restart is required... any active sessions will be resumed automatically". Right now the UI promises the operation it no longer performs.DB damage recurrence (2026-10-25, opencode-mem, turso/libsql):
<db>-waltruncated to 0 bytes next to a stale<db>-tshm(86,016 bytes). Every later open fails withI/O error: short read on WAL frame at offset N: expected 4096 bytes, got 0. Auto-capture retries 3x, then user-profile learning and idle processing abort on the same error.user-profiles.dblast successful write 2026-10-20 22:59, first failure 2026-10-21 00:00:43. Second event leftai-sessions.db,metadata.dbandskills.dball stamped 2026-10-22 06:06:14..dbfiles passedPRAGMA integrity_check— the damage was sidecar-only. The btree/rowid failures I reported earlier were likely a worse instance of the same truncation rather than a separate defect.-walbeside a-tshmis also the normal shape of an idle DB (turso recreates both on demand), and neither sqlite3 nor python's sqlite3 reproduces turso's own shm validation. So this can only be repaired, not cleanly detected: verify the main DB, delete the sidecars, let turso rebuild. That heals in place, no restart needed, no data loss.opencode serve --service), not in the TUI, so a hard stop takes every plugin database down at the same moment.Ask: whatever the exit path does, plugins need a chance to close or checkpoint SQLite handles before the process dies — a SIGTERM with a grace period, or an awaited plugin shutdown hook. That converts this from data damage into a clean shutdown regardless of whether Restart ever relaunches.
Happy to run diagnostics against my curl install — the exit path is reproducible on demand here, which is what the earlier reply said was missing for triage.
jkemp814 commentedon Oct 26, 2026
Follow-up with the host-side half, since the earlier reply said triage needed a real curl install. Measured on 2.1.4 (
~/.opencode/bin/opencode, channellatest), Fedora Silverblue.The restart path, as executed:
service restart=St.stop()thenSt.ensure().St.stop()runs the PTY handoff, then:SIGTERMto theopencode serve --servicepid;kill(pid, 0)) everystopPollInterval: 50ms forstopPollAttempts: 100— 5 s total;SIGKILL, then another liveness wait.So the dialog's "restart" is: kill the shared server with a 5 s tolerance, then try to start a new one. On this install the start half doesn't happen (TUI exits to the shell), which is the behaviour already reported above.
Why this damages plugin state. Plugins live in the shared server, not the TUI — the same process that gets SIGTERM/SIGKILL. The plugin API declares
Hooks.dispose?: () => Promise<void>, but I could not find any call site that invokes a server plugin'sdisposeon shutdown; every.dispose?.()hit in the bundle is TUI/renderable-side. What does exist is the runtime's ownprocess.on("SIGTERM")→ interrupt the main fiber → teardown →process.exit(), which can cut an async plugin cleanup short. A plugin's only reliable chance is therefore its own SIGTERM handler, racing the 5 s budget.Concrete consequence (turso/libsql databases, opencode-mem):
metadata.dbWALuser-prompts.dbWALThat is the healthy direction — with a pre-close
PRAGMA wal_checkpoint(TRUNCATE)in the plugin, the WAL is flushed on the way out and nothing is left half-written. Without it, the same databases have twice been left with a 0-byte-walbeside a stale-tshm, after which every open fails withI/O error: short read on WAL frame at offset N: expected 4096 bytes, got 0and the whole memory subsystem (tools, capture, web server) is gated off.Plugin-side fix, if useful as a reference: tickernelz/opencode-mem#440 — checkpoint before
database.close(), bounded so it cannot outlast a shutdown window.Host-side ask (this is the part a plugin can't fix): whichever path tears the server down should either call plugin
disposeand await it, or SIGTERM with a grace period longer than the cleanup needs, rather than a fixed 5 s followed by SIGKILL. And separately: if relaunching really is gone per #47364, the button and copy should say "Shut down — runopencodeagain", because "Restart" with "sessions will be resumed automatically" is currently a promise the code doesn't keep.Happy to test a diagnostic build — the exit path is reproducible on demand here.