anomalyco / anomalyco/opencode
ACP: session/new persists an empty session row that never gets cleaned up
@rekram1-node is already working on this.
Since Jul 21, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Summary
opencode acp creates and persists a durable session in SQLite the moment an ACP client (Zed, Neovim, etc.) issues session/new, before any prompt is ever sent. If the user then closes the editor panel, switches agents, or the client disconnects without prompting, the empty session row is orphaned forever — there is no cleanup on close and no session/delete in ACP 0.21.
Zed's agent panel triggers this routinely: opening the panel or picking the opencode agent from the dropdown provisions a new session eagerly. Every such interaction that the user abandons before typing leaves behind a New session - <ISO> row that keeps reappearing in the session picker.
Verified against upstream/dev at 849c2598abc7d2b40261e74b5826bc74ffc78308 (post-v1.18.4).
Reproduction
opencode auth loginand configure a working model.- In Zed (or any ACP client), open the agent panel and pick opencode.
- Do not send any prompt. Close the panel, or switch to another agent, or quit the editor.
- Reopen the agent panel and list sessions (Zed's session history, or
opencodeTUI's session list). - An empty session titled
New session - 2026-07-21T...(zero messages, zero cost) appears and persists across restarts.
Repeat a few times → the picker fills with orphaned stubs.
Mechanism
newSession persists eagerly. packages/opencode/src/acp/service.ts:161-207 calls input.sdk.session.create({...}) unconditionally. That resolves to V2Session.create which:
- Inserts a
ProjectTablerow, - Builds a
SessionInfowithtitle: "New session - ${new Date(now).toISOString()}"(session.ts:228), - Publishes
SessionV1.Event.Created, which theSessionProjectorsynchronously inserts intoSessionTable.
By the time session/new returns, the row is durable — zero messages, zero cost, timestamp-placeholder title.
closeSession doesn't delete — it only aborts. service.ts:339-347:
const closeSession = ...(function* (params) {
const removed = yield* session.remove(params.sessionId) // in-memory Map only
registeredMcp.delete(params.sessionId)
sessionSnapshots.delete(params.sessionId)
if (!removed) return {}
yield* abortBackingSession(removed) // POST /session/abort
return {}
})
session.remove (in packages/opencode/src/acp/session.ts) only clears the in-process Map<string, Info>. abortBackingSession calls session.abort, not session.delete. Nothing removes the row.
No session/delete in ACP. The SessionCapabilities schema in @agentclientprotocol/sdk@0.21.0 only exposes list, resume, fork, close. SessionCloseCapabilities docs are explicit: "By supplying {} it means that the agent supports closing of sessions." Close ≠ delete. Even if a client had a "delete" UI action, it would still resolve to session/close.
listSessions surfaces them forever. service.ts:244-288 merges sdk.session.list() (durable) with in-memory ACP sessions and returns both. Orphaned stubs from prior runs keep showing up.
Contrast with the TUI
opencode's own TUI creates the session lazily inside SessionV2.prompt(...) on first prompt, so no empty rows accumulate. Only the ACP entry point has this eager-persist behavior.
Suggested fixes (cheapest → most correct)
- Cleanup on close. In
closeSession, if the backing session has zero messages, callsdk.session.delete(...)afterabortBackingSession. Handles graceful disconnect (Zed closing the panel). - Cleanup on connect. At the start of each ACP connection, sweep sessions under the project directory that have zero messages and were created by ACP. Cheap heuristic (
SessionMessage count == 0+ a "created via ACP" marker). Handles crash/restart. - Defer persistence (the real fix). Have
newSessionallocate a session ID without projectingSession.Created. Only persist on the firstsession/prompt. Aligns ACP with the TUI's lazy-persist behavior and eliminates the class of bug rather than sweeping it up. Requires plumbing an "unprojected" state throughSessionV2.prompt.
(1) + (2) together are a solid stopgap needing no protocol changes. (3) is the correct long-term fix.
Environment
- Verified reading:
sst/opencode@849c2598ab(upstream/dev, post-v1.18.4) - ACP SDK:
@agentclientprotocol/sdk@0.21.0 - Client: Zed (agent panel); reproducible with any ACP client that eagerly calls
session/new.
Related
- #35457 (Port ACP support to V2 core and APIs) — umbrella for the V2 port; doesn't mention this specific empty-session behavior. Regardless of the V2 migration, both
newSessionandcloseSessionon the current V2-compat code path exhibit this bug.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.