Repository navigation
opencode session list hangs for ~2 minutes due to unnecessary full InstanceBootstrap #37435
Description
Summary
Running opencode session list blocks the terminal for approximately 2 minutes before showing results, even though the underlying SQL query completes in ~0.007 seconds. The root cause is that the command triggers a full InstanceBootstrap (config loading, plugin init, LSP startup, VCS init, file watchers, snapshot system, etc.) — all of which are unnecessary for a simple read-only database query.
Environment
- opencode version: 1.18.x (current
devbranch) - Database: 94 sessions, SQLite WAL mode, 131 MB after VACUUM
Steps to reproduce
time opencode session list
# Blocks for ~2 minutes before printing the session tableRoot cause
1. SessionListCommand does not set instance: false
packages/opencode/src/cli/cmd/session.ts:70-86:
export const SessionListCommand = effectCmd({
command: "list",
describe: "list sessions",
// ← missing `instance: false`
builder: (yargs) => ...
handler: Effect.fn("Cli.session.list")(function* (args) {
const sessions = yield* Session.Service.use((svc) => svc.list(...))
...
}),
})The instance option in effectCmd defaults to true (packages/opencode/src/cli/effect-cmd.ts:46):
/**
* Whether the command needs a project InstanceContext. Defaults to true.
*
* `true` (default): ... Runs InstanceBootstrap (config + plugin
* init + LSP/File/etc forks) eagerly.
*
* `false`: skip the instance entirely. ... Use `false` for commands
* that don't read project state (e.g. `models`, `serve`, `web`, `account`, `db`, `upgrade`).
*/
instance?: boolean | ((args: Args) => boolean)When instance: true (the default), effectCmd loads the full InstanceStore, which triggers InstanceBootstrap.run:
packages/opencode/src/project/bootstrap.ts:32-46:
const run = Effect.gen(function* () {
yield* config.get() // load configuration
yield* plugin.init() // initialize plugins (can mutate config)
yield* Effect.forEach(
[lsp, shareNext, format, vcs, snapshot, project],
(s) => s.init() // start LSP, file watchers, VCS, snapshot, etc.
...
)
})This boots the entire opencode runtime — including starting language servers, file watchers, git monitors, snapshot system, share service, and formatting service — just to run a single SELECT query on the session table that takes 0.007 seconds.
2. Session.list() requires InstanceState.context
packages/opencode/src/session/session.ts:548-555:
const list = Effect.fn("Session.list")(function* (input?: ListInput) {
const ctx = yield* InstanceState.context // ← requires full instance
return yield* listByProject(db, {
projectID: ctx.project.id, // ← used for WHERE filter
...
})
})Because list() yields InstanceState.context to get ctx.project.id, simply setting instance: false would cause a defect (the InstanceRef would be undefined).
3. Other commands already skip InstanceBootstrap
Multiple commands correctly set instance: false and work fine:
| Command | File | instance |
|---|---|---|
db |
db.ts:11 |
false |
account (all subcommands) |
account.ts:180,195,210,220,230 |
false |
providers |
providers.ts:253,500 |
false |
serve |
serve.ts:12 |
false |
web |
web.ts:37 |
false |
session list |
session.ts:70 |
not set → defaults to true ❌ |
4. Evidence that SQL is not the bottleneck
-- Query plan shows indexed scan + temp sort (expected for ORDER BY without covering index)
EXPLAIN QUERY PLAN
SELECT * FROM session WHERE project_id=? AND parent_id IS NULL
ORDER BY time_updated DESC LIMIT 100;
-- |--SEARCH session USING INDEX session_project_idx (project_id=?)
-- `--USE TEMP B-TREE FOR ORDER BY
-- Actual execution time: ~0.007 secondsThe database has 94 sessions, 3,806 messages, and 16,449 parts. The session table scan is trivially fast even without a covering index on time_updated.
Proposed fix
Two possible approaches:
Option A (simpler): Bypass InstanceState in the CLI handler
Modify SessionListCommand to set instance: false and query sessions directly via Database.Service + directory filter (instead of project_id), following the same pattern used by the db command:
export const SessionListCommand = effectCmd({
command: "list",
instance: false, // ← ADD THIS
...
handler: Effect.fn("Cli.session.list")(function* (args) {
const { db } = yield* Database.Service
const rows = yield* db
.select()
.from(SessionTable)
.where(and(
eq(SessionTable.directory, process.cwd()),
isNull(SessionTable.parent_id)
))
.orderBy(desc(SessionTable.time_updated))
.limit(args.maxCount ?? 100)
.all()
// ... format and output
}),
})Option B: Add a lightweight listByDirectory method to the session service
Create a new method that doesn't require InstanceState.context, using directory as the filter key instead of project_id.
Additional note
The same issue likely affects session delete (same file, same missing instance: false), as well as the stats command which also queries sessions via the full InstanceBootstrap.
Activity
armancharan commentedon Jul 17, 2026
I'd like to take this. Plan is to skip InstanceBootstrap for
session list(and the same read-only siblings if they share the footgun) by settinginstance: falseand listing via directory instead of requiring full InstanceState.wsaaaqqq commentedon Jul 27, 2026
The 2-minute wait is brutal. oos skips InstanceBootstrap entirely by reading SQLite directly.
Built a workaround: oos
It reads the OpenCode SQLite db directly, so it searches ALL sessions from
ALL projects at once — no directory scoping, no pagination limits, no
subagent dilution.
https://github.com/wsaaaqqq/oos
MIT, one-liner install. Happy to take feedback.
MuskanPaliwal commentedon Aug 14, 2026
I ran into what might be another symptom of this while looking into entireio/cli#1992.
With OpenCode 1.18.16, running:
rewrote
.opencode/package-lock.json. I’m not sure yet whether PR #37477 fixes that behavior, but it seems possible since the PR avoidsInstanceBootstrapand plugin initialization.I’ve left a note on the PR asking whether a filesystem-level regression test might be useful here, especially for callers running
session listinside a dirty worktree.github-actions commentedon Oct 15, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.