Skip to content

opencode session list hangs for ~2 minutes due to unnecessary full InstanceBootstrap #37435

Closed as not planned

Description

@suxiaogang223

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.

Activity

  1. armancharan commented on Jul 17, 2026

    @armancharan

    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 setting instance: false and listing via directory instead of requiring full InstanceState.

  2. wsaaaqqq commented on Jul 27, 2026

    @wsaaaqqq

    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.

    • Type a keyword → real-time filter across every project
    • ↑/↓ to pick, Enter to resume the conversation
    • Multi-keyword AND + ! exclusion
    • Zero config, single binary

    https://github.com/wsaaaqqq/oos

    MIT, one-liner install. Happy to take feedback.

  3. MuskanPaliwal commented on Aug 14, 2026

    @MuskanPaliwal

    I ran into what might be another symptom of this while looking into entireio/cli#1992.

    With OpenCode 1.18.16, running:

    opencode --pure session list --format json

    rewrote .opencode/package-lock.json. I’m not sure yet whether PR #37477 fixes that behavior, but it seems possible since the PR avoids InstanceBootstrap and 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 list inside a dirty worktree.

  4. github-actions commented on Oct 15, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions