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
dev branch)
- 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 table
Root 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 seconds
The 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.
Summary
Running
opencode session listblocks 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 fullInstanceBootstrap(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
devbranch)Steps to reproduce
Root cause
1.
SessionListCommanddoes not setinstance: falsepackages/opencode/src/cli/cmd/session.ts:70-86:The
instanceoption ineffectCmddefaults totrue(packages/opencode/src/cli/effect-cmd.ts:46):When
instance: true(the default),effectCmdloads the fullInstanceStore, which triggersInstanceBootstrap.run:packages/opencode/src/project/bootstrap.ts:32-46: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
SELECTquery on thesessiontable that takes 0.007 seconds.2.
Session.list()requiresInstanceState.contextpackages/opencode/src/session/session.ts:548-555:Because
list()yieldsInstanceState.contextto getctx.project.id, simply settinginstance: falsewould cause a defect (theInstanceRefwould be undefined).3. Other commands already skip InstanceBootstrap
Multiple commands correctly set
instance: falseand work fine:instancedbdb.ts:11falseaccount(all subcommands)account.ts:180,195,210,220,230falseprovidersproviders.ts:253,500falseserveserve.ts:12falsewebweb.ts:37falsesession listsession.ts:70true❌4. Evidence that SQL is not the bottleneck
The database has 94 sessions, 3,806 messages, and 16,449 parts. The
sessiontable scan is trivially fast even without a covering index ontime_updated.Proposed fix
Two possible approaches:
Option A (simpler): Bypass
InstanceStatein the CLI handlerModify
SessionListCommandto setinstance: falseand query sessions directly viaDatabase.Service+directoryfilter (instead ofproject_id), following the same pattern used by thedbcommand:Option B: Add a lightweight
listByDirectorymethod to the session serviceCreate a new method that doesn't require
InstanceState.context, usingdirectoryas the filter key instead ofproject_id.Additional note
The same issue likely affects
session delete(same file, same missinginstance: false), as well as thestatscommand which also queries sessions via the full InstanceBootstrap.