Repository navigation
Conversation
…f the main thread (STA-9122) Before: the OpenCode Go key lookup opened opencode.db, and every Codex usage refresh, hook trust grant, account bridge, and Codex terminal launch opened Codex's state_<n>.sqlite and walked its sessions tree, all with synchronous calls on the Electron main thread. A large -wal or a stalled \\wsl.localhost share froze the window for as long as the open took. After: both run on the foreign SQLite reader worker, each on its own lane. - openCodeGoKey reader: takes the claim-ordered database list (discovery stays async and outside; WSL paths are still skipped before dispatch) and returns found / missing / unreadable. An unanswered worker is `unreadable`; the public helper still maps everything but `found` to null, so behaviour is unchanged and the separate "stop before OPENCODE_API_KEY" fix only has to return the tri-state. - codexIndexStatus reader: one lane answers three queries (backfill status plus a bounded rollout count, indexed thread ids, session-file count). The newest-DB lookup and the sessions walk moved into it too, so a WSL Codex home is never opened or listed from the main thread. codex-state-db's public functions are now async; an unanswered worker returns each one's existing failure value (unreadable / not pending / null / 0). - The lane now returns null for "worker did not answer" and each client method maps it to its failure value, so callers can tell it apart from an unreadable database. Backfill recovery polling keeps polling on null and still stops on a real `unreadable`; its first read spawns nothing on null. - Recovery dedupe: startCodexStateDbBackfillRecoveryInBackground reserves its activeRecoveries slot before awaiting the pending read, so concurrent starts share one task and ensureCodexStateDbBackfillRecoveryStarted waits on that slot's lock arbitration. A not-pending answer releases the slot. - A lane holds a ref'd handle while a read is in flight: the worker is unref'd, and the orca CLI (which reaches the trust grant) otherwise exited mid-read with nothing else alive. - The supervisor lock moved to its own module to keep recovery under max-lines; tsconfig.cli.json lists the new CLI closure. Tests: both readers on real fixtures; dispatch routing; client lanes (answer, unanswered, mismatched answer); async callers via an in-process test worker that runs the real dispatch; two concurrent recovery starts produce one recovery and ensure waits for the pending read; poll-on-unanswered; WSL home on pinned win32 posts to the worker with no main-thread fs or SQLite touch; the bundled worker answers both readers; and a plain-Node child process stays alive until its read answers (fails without the keep-alive).
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- OpenCode Go key off the main thread — new
openCodeGoKeyreader;readOpenCodeCredentialDatabaseGoKeydiscovers paths, filters WSL UNC paths before dispatch, then reads on the worker and maps anything other thanfoundtonull, so the public result is unchanged. - Codex index off the main thread — one
codexIndexStatusreader answers backfill status plus a bounded rollout count, indexed thread ids, session-file count, and newest-DB discovery. Thecodex-state-dbpublic functions are nowasync, and every caller (codex-fetcher,codex-hook-trust-grant, account bridge, index heal, backfill recovery, spawn preflight/env) awaits them. - Recovery coordinator — reserves the
activeRecoveriesslot synchronously before awaiting the asyncisPending, uses a'not-pending'sentinel for "no recovery needed", and keeps "worker did not answer" (keep polling / retry) distinct from a genuinelyunreadableindex. - Worker client —
ForeignSqliteReaderLane.readreturnsT | nulland callers map the failure themselves; a ref'd keep-alive handle holds a short-lived CLI process open until a read settles. - Supporting — supervisor lock moved to
codex-state-db-backfill-supervisor-lock.tsto stay under the line cap; in-process test worker added; the new import graph added toconfig/tsconfig.cli.json.
I traced the async rewrites against their pre-PR synchronous behaviour across the failure cases (no state DB, corrupt DB, missing backfill_state/threads/credential tables, empty key, WSL paths, and worker no-answer) and verified the coordinator's slot lifecycle under concurrent starts, ensure, gave-up/failure-cap settlement, and stop. The only newly reachable states are the intended "reader did not answer" ones, bounded by the reader's 60 s timeout, and the tests for them assert exact values (the CLI keep-alive test in particular fails without the fix).
ℹ️ Author-flagged packaged-CLI check
The PR notes say the built out/main/foreign-sqlite-readers/ entry "isn't in asarUnpack". In the current tree it is — config/electron-builder.config.cjs:313 unpacks out/main/foreign-sqlite-reader-entry.js, alongside out/main/chunks/**, out/shared/**, out/cli/**, and node_modules/zod/**, and foreign-sqlite-reader-spawn.ts resolves it through findForeignSqliteReaderEntry's parent lookup. Worth re-confirming the note is stale before merge; no code change needed here.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
|
Closing: after #24572 fixed the reported Cursor freeze, we're keeping this effort to the OpenCode binder. Moving the Codex and Go key reads off the main thread is about 1,600 lines and makes Codex's checks async across terminal launch and recovery, with no reported freeze to justify the risk. The separate correctness fixes stay in #24604 and #24605. |

ELI5
On every usage refresh, Orca reads OpenCode's database to find your Go key. It also reads Codex's index on every Codex usage refresh and every Codex terminal launch, to check whether Codex is rebuilding it. Both reads ran on the thread that draws Orca's window, so a large database could freeze the app. This PR moves both reads to background threads.
What Changed
openCodeGoKeyreader that answers found, missing or unreadable.\\wsl.localhostskip happens before anything is sent to the worker.null.codexIndexStatusreader thread now does all ofcodex-state-db.ts's disk work: backfill status with a bounded session-file count, indexed thread ids, session-file count, the newest-database lookup, and the sessions directory walk.startCodexStateDbBackfillRecoveryInBackgroundnow reserves itsactiveRecoveriesslot before awaiting, so two concurrent starts share one recovery andensure…Startedwaits for the lock to be arbitrated.unreadable.\\wsl.localhostare read inside the reader thread, so a hung share stalls only that thread, up to its 60 s timeout. Recovery supports WSL homes on purpose, so failing early would have silently switched it off for them.orcaCLI also reaches the trust grant. Reader threads now keep the process alive while a read is in flight (the CLI used to exit mid-read), and the new files are added toconfig/tsconfig.cli.json.codex-state-db-backfill-supervisor-lock.ts, to stay under the 300-line limit.Why
Same approach as #24603 and #24638: one thread per reader on the shared worker, and never a main-thread fallback. Measured terminal-launch cost is about 26 ms when the Codex reader thread is cold (median of 15 runs, max 30 ms) and about 0.4 ms when it's warm, compared with about 0.1 ms inline. Launches only pay the cold cost when no Codex index read happened in the last 30 s.
Linked Issue
Related to #24360. Stacked on #24638, which must land first: orcad runs this Codex code and only gets the worker in #24638.
Visual Proof
N/A. No UI change.
Testing
process.platformpinned;pnpm tcpasses.oxlintandcheck:code-quality:changedpass.pnpm build:orcadpasses, smoke check included.Review
Agent skill upstream boundary
Notes
orcaCLI loadsout/main/foreign-sqlite-readers/fromapp.asar. It isn't inasarUnpack, the same as thesync-databasecode the CLI already loads, but nobody has checked a packaged build yet.unreadable.Checklist
N/Awith reason for visual proof