Repository navigation
Description
Summary
The desktop app shows "ghost" sessions that exist only in local state (drafts.sqlite) but not on the server. Deleting them calls DELETE /api/session/{id}, the server responds 404 SessionNotFoundError, and the app treats this as a failure without cleaning up its local references — so the ghost entry can never be removed from the UI.
Environment
- opencode version: 2.0.18 (desktop app, sidecar service
opencode-cli.exe serve --service) - OS: Windows_NT 10.0.26200 (win32 x64)
- Terminal: N/A (desktop app)
- Shell: cmd.exe / PowerShell
- Install/channel: desktop app, latest
- Active plugins: oh-my-openagent (fails to load: "Plugin must export a default definition with an id and an effect or setup function")
Reproduction
- Get a session entry in the desktop app that has no server-side record. On my machine this happened naturally: during a service port conflict (see Additional Context), 8 sessions were created in a ~28h window and were never persisted to the server database.
- The ghost sessions still appear in the workspace session list — local references survive in
%APPDATA%\ai.opencode.desktop\drafts.sqlite:documenttable:opencode.workspace.<ws>.dat:session:<id>:prompt(empty prompt draft)statetable:opencode.browser.dat|sidecarrow keyed by the session id
- Click delete on such a session.
- The app calls
DELETE /api/session/<id>→ server responds404 {"_tag":"SessionNotFoundError",...}→ the app reports a delete failure and keeps the entry. - The entry is permanently undeletable from the UI.
Expected Behavior
A 404 SessionNotFoundError on delete should be treated as "already gone": remove the local references (prompt draft, browser sidecar state) and drop the entry from the list. Optionally, the session list should not render entries that have only local draft state with no server backing (or should render them as discardable drafts).
Actual Behavior
Delete fails with an error and the ghost session stays in the list forever. Verified server-side evidence:
- Server log:
DELETE /api/session/ses_f16d31626ffeONweIVod05774k http.status=404(attempts at 2026-09-28T18:01:52Z and 2026-09-28T20:21:43Z, and again after a full service restart) - The session id is absent from both
session_v2andsessiontables in the server database (checked read-only) - All 8 ghost sessions have empty prompt drafts and no messages anywhere
Additional Context
Origin of the ghost sessions (why they have no server record): on 2026-09-28 between 17:41-17:53 UTC the log shows dozens of Managed service port 49374 on 127.0.0.1 is already in use by another process errors (then port 49375 as well) — multiple service instances were fighting. Sessions created in that window were never persisted; after the service situation settled, they only lived on in the desktop app's local state.
Workaround (manual, requires quitting the app): delete the leftover rows from drafts.sqlite — the document row ...:session:<id>:prompt and the state row opencode.browser.dat|sidecar / <session id> — then restart the app.
Frequency: consistently reproducible for every ghost session; the creation of ghosts itself was a one-time event tied to the service conflict window.
Activity
github-actions commentedon Sep 28, 2026
This issue might be a duplicate of an existing issue. Please check:
DELETE /api/session/{id}fails.opencodejr commentedon Oct 28, 2026
Thanks for the detailed report, including the server log and the
drafts.sqlitedetails. I couldn't reproduce this here: it needs the desktop app, and this sandbox can only run the CLI/TUI, which uses a different client.Reading the current code, the behaviour you describe looks expected from it. When the server answers 404
SessionNotFoundError, the desktop delete handles it like any other failure. It shows the "delete failed" toast and never runs the cleanup that drops the tab and local session state, so an entry with no server record can't be removed from the UI. A maintainer with the desktop app will need to confirm it. Until then, the manual cleanup ofdrafts.sqliteyou described (with the app closed) is the workaround.