Skip to content

Unbounded duplicated thread metadata can crash Desktop startup reconciliation via better-sqlite3 .all() #29007

Open

Description

@kangminlee-maker

Summary

Codex Desktop can crash during startup before the window is ready when the local profile contains a large ~/.codex/sqlite/state_5.sqlite with oversized threads display metadata.

The immediate crash trigger is in the packaged Electron main-process startup reconciliation path that copies/merges temporary app-server state from:

  • source: ~/.codex/sqlite/state_5.sqlite
  • target: ~/.codex/state_5.sqlite
  • marker: ~/.codex/.app-server-state-reconciled-v1

The broader structural issue is that bounded UI/list metadata and unbounded conversation content are mixed together. Fields such as title, preview, and first_user_message are used by startup/list/navigation hot paths, but can contain full prompt-sized content and can be duplicated across all three fields.

In the current packaged app, the startup reconciliation code still eagerly materializes all thread rows:

for (let row of sourceDb.prepare(`SELECT ${columns} FROM threads`).all()) {
  ...
}

If columns includes very large title, preview, and first_user_message values, this loads the full thread metadata table into V8 memory and can terminate the Electron main process with a V8 OOM. macOS records this as EXC_BREAKPOINT / SIGTRAP.

Environment

Original crash:

  • Codex Desktop: 26.611.62324 (4028)
  • Codex Framework / Chromium: 149.0.7827.115
  • macOS: 26.4.1 (25E253)
  • Architecture: Apple Silicon / arm64
  • Process: /Applications/Codex.app/Contents/MacOS/Codex

After reinstall/update, I rechecked the current app:

  • Codex Desktop: 26.616.31447 (4133)
  • app-server / CLI: 0.142.0-alpha.1
  • better-sqlite3: 12.9.0
  • Codex Framework still: 149.0.7827.115

The app currently launches after manually repairing/trimming the affected DB state, but the same vulnerable startup reconciliation code path is still present in the updated app bundle.

Crash signature

Relevant macOS crash stack excerpt:

Exception Type: EXC_BREAKPOINT (SIGTRAP)
Termination Reason: SIGNAL, Trace/BPT trap: 5
Triggered by Thread: 0 CrBrowserMain

v8::String::NewFromUtf8(...)
better_sqlite3.node Data::GetValueJS(v8::Isolate*, sqlite3_stmt*, int, bool)
better_sqlite3.node Statement::JS_all(...)
...
ChromeMain
main

Controlled launch attempts also produced V8 OOM stderr:

OOM error in V8: Scavenger: semi-space copy Allocation failed - JavaScript heap out of memory

Local DB evidence

The source DB read by the startup reconciliation path was valid SQLite, not corrupt:

PRAGMA quick_check = ok
threads = 29141

Before manual repair, the threads table contained very large duplicated display metadata:

max(length(title))              = 657278
max(length(preview))            = 657278
max(length(first_user_message)) = 657278

rows where title = preview = first_user_message:
29137 / 29141

sum(title + preview + first_user_message):
~2152.4 MB raw SQLite text

Most of this was active exec thread metadata:

archived=0 rows: 28438, ~2150.1 MB metadata
source=exec rows: 27985, ~2147.6 MB metadata

After trimming the source DB to bounded metadata and creating/allowing the reconciliation marker, the app launched successfully.

Current updated-app check

After reinstalling/updating to 26.616.31447 (4133), I extracted the packaged app.asar and confirmed the startup reconciliation code still uses .all() over threads.

The repaired ~/.codex/sqlite/state_5.sqlite is now bounded:

threads = 29141
max title / preview / first_user_message = 200 / 4000 / 4000
over limit = 0 / 0 / 0

However, the live root DB is already accumulating oversized metadata again after normal use:

~/.codex/state_5.sqlite
threads = 31540
max title / preview / first_user_message = 76211 / 76211 / 76211
over limit rows = 39 / 18 / 18
source = exec

So the immediate startup crash is currently avoided by the repaired source DB and marker, but the underlying unbounded metadata producer still appears active.

Why this is not just a one-line .all() bug

The .all() call is the immediate trigger, but the deeper invariant violation is:

thread display metadata is unbounded and duplicated, while multiple hot paths assume it is small.

title, preview, and first_user_message are used like lightweight projections for sidebar/list/startup/reconciliation paths. In my affected DB they had become transcript-sized payloads, and the same large string was usually stored three times.

That turns a valid local DB into a poisoned state: any path that treats those columns as small metadata can become a memory, CPU, IPC, or renderer problem.

Related issues

This appears related to the local-history / unbounded metadata family, especially:

This report is a more severe startup-specific failure mode:

Codex Desktop can crash before the UI is ready because Electron main-process startup reconciliation calls better-sqlite3 .all() over all threads rows, including unbounded metadata columns.

Expected behavior

  • Local thread metadata should not be able to crash the Desktop app during startup.
  • Startup reconciliation should not materialize all thread rows and all large text columns into V8 at once.
  • Display metadata fields such as title, preview, and list/read summaries should be bounded.
  • Existing oversized local state should be repaired, truncated to safe projections, or degraded gracefully.

Actual behavior

  • Codex Desktop crashed during startup with EXC_BREAKPOINT / SIGTRAP.
  • The meaningful native stack points to better_sqlite3.node Statement::JS_all converting SQLite text into V8 strings.
  • Manual DB trimming was required to recover the app.

Suggested fixes

  1. Replace startup reconciliation .all() calls with streaming or paged iteration, e.g. iterate() or keyset paging.
  2. Avoid selecting large display fields unless needed.
  3. In reconciliation SQL, cap projected metadata fields, for example:
    • title: short display label
    • preview: bounded preview
    • first_user_message: bounded snapshot or excluded from hot paths
  4. Enforce metadata caps at write time and migration/reconciliation boundaries.
  5. Treat raw conversation history/session JSONL as the source of truth for full content; keep threads metadata as bounded projections.
  6. Add a repair/doctor path for existing oversized threads rows.
  7. Keep startup recovery non-fatal where possible: log and continue, or show a repair prompt, instead of letting V8 OOM terminate the app.

Workaround used locally

I backed up both DBs, then trimmed:

  • title <= 200
  • preview <= 4000
  • first_user_message <= 4000

After that, creating/allowing the .app-server-state-reconciled-v1 marker let the app launch normally.

I am not attaching raw DB rows or full crash reports publicly because they may contain private local paths and conversation content, but I can provide additional sanitized excerpts if useful.

Activity

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    app-serverIssues involving app server protocol or interfaces
    on Jun 19, 2026
  2. Gusarich commented on Jun 19, 2026

    @Gusarich

    Also hitting this.

    • Codex Desktop: 26.616.30709 (4108)
    • Codex Framework / Chromium: 149.0.7827.115
    • macOS: 27.0 (26A5353q)
    • Apple Silicon (M2 Max), 32 GB

    Crash is EXC_BREAKPOINT (SIGTRAP) on thread 0 CrBrowserMain, ~7s after
    launch (before the window appears). Native stack matches.

  3. Gusarich commented on Jun 19, 2026

    @Gusarich

    Same happens on latest 26.616.31447 (4133)

  4. Gusarich commented on Jun 22, 2026

    @Gusarich

    still happens on 26.616.51431 (4212)

    reverting to 26.601.21317 helps

  5. Gusarich commented on Jun 25, 2026

    @Gusarich

    The workaround suggested in this issue helps. But I wish OpenAI fixed it properly on their side.

  6. navneet-livio commented on Aug 1, 2026

    @navneet-livio

    Cross-linking #36523 — same structural failure mode you describe here ("bounded UI/list metadata and unbounded conversation content are mixed together… eagerly materializes all rows"), but in a different startup path.

    In my case it isn't sqlite, it's external-agent-import: on every launch it opens 4,463 local_*.json session-metadata files totalling 1.72 GB under ~/Library/Application Support/Claude/local-agent-mode-sessions/ — a different application's directory — and parses them all into the JS heap. Startup RSS peaks at 2,857 MB against V8's ~3.5 GB ceiling, so each launch is a coin flip. Same EXC_BREAKPOINT / SIGTRAP outcome.

    Measured evidence: archiving stale metadata (4,463 files → 45, keeping the last 3 days) dropped peak startup RSS to 1,280 MB and produced five consecutive clean cold launches. Renaming the directory out of the scan path entirely dropped the import scan from 5,900 ms → 39 ms.

    Raising it here because your framing generalises: the fix that would close all of these is an aggregate byte cap on anything materialized during startup, rather than per-path bounds added one at a time. Right now at least three separate startup paths can each independently exhaust the heap (sqlite thread metadata here, external-agent-import in #36523, and the OOM-on-every-launch in #32192).

  7. boombx403-byte commented on Aug 18, 2026

    @boombx403-byte

    Hi @kangminlee-maker, your report looks related to a local session discovery/inventory gap rather than necessarily a lost rollout. I've released Codex Rescue Alpha5, a local read-only diagnostic tool that performs filesystem-first discovery across active and archived rollouts and cross-references SQLite/index metadata without modifying any state.

    If you still have the affected local Codex directory, you can inspect it via:

    pip install codex-rescue==0.1.0a5
    codex-rescue sessions --json

    No raw files or databases are needed. If you choose to share output, please review and sanitize any private repository paths or names first.

  8. Audacity88 commented on Sep 2, 2026

    @Audacity88

    I packaged a reversible local workaround for this specific metadata-bloat failure:

    https://github.com/EyrieCommander/codex-skills/tree/master/skills/keep-codex-fast

    Its first run is report-only. The explicit --repair-thread-metadata-bloat mode backs up the affected state, bounds only SQLite display metadata such as title, preview, and first-user-message fields, preserves the durable rollout transcript, and writes a restore manifest and script. When possible, it restores a friendly title from the session index instead of retaining a truncated prompt-derived title.

    This is an unofficial recovery tool, not a fix for the underlying unbounded metadata producer or eager history hydration. Codex should be fully closed before applying repairs.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    appIssues related to the Codex desktop appapp-serverIssues involving app server protocol or interfacesbugSomething isn't workingperformance

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions