Skip to content

macOS desktop: repeated disk writes resource-limit kills from runaway SQLite WAL checkpoint on unbounded logs_2.sqlite; stale SingletonLock then blocks relaunch #32431

Open

Description

@ZenAlexa

TL;DR

On macOS, the desktop app's background codex helper is repeatedly terminated by the OS disk writes resource monitor (jetsam / RLIMIT-style highwater kill), not by an in-app crash. Root cause: the local logging database ~/.codex/logs_2.sqlite has grown to ~2.8 GB and the process spends effectively all of its time in sqlite3_wal_checkpoint_v2 → pwrite, dirtying tens of GB of file-backed memory (worst observed: 34.36 GB of file-backed memory dirtied over 23,191 s, ~1.48 MB/s sustained). macOS counts this against a per-process disk-write highwater limit and kills the process.

Because the kill is unclean, it leaves behind:

  • a stale SingletonLock in ~/Library/Application Support/Codex/ pointing to a dead PID, and
  • orphaned helper processes (browser_crashpad_handler ×2, bare-modifier-monitor) reparented to PID 1,

which then prevent the app from relaunching ("nothing happens when I open it") until they are manually cleared.

This is two related defects: (A) the runaway-write termination, and (B) the failure to recover cleanly on next launch.


Environment

Hardware Mac16,8 (Apple Silicon, arm64e), 12 CPUs, 24 GB RAM
OS macOS 26.4 (build 25E246)
App ChatGPT desktop (formerly "Codex"), app version 26.707.41301
Framework Codex Framework.framework version 150.0.7871.115 (previously .101 — see note below)
Codesigning ID / Team codex / 2DC432GLL2
Resource Coalition com.openai.codex

Note: crash reports show the bundle path migrating from /Applications/Codex.app/... (≤ Jul 9) to /Applications/ChatGPT.app/... (≥ Jul 10), i.e. a rebrand/update happened mid-window. Both bundles exhibit the identical failure signature.


Defect A — OS terminates the process for excessive disk writes (runaway SQLite WAL checkpoint)

OS-level termination signature

Every one of the last 9 diagnostic reports is a disk writes Microstackshots report (Action taken: none), i.e. the macOS disk-write resource monitor, not a segfault/abort. Most recent report (codex_2026-07-11-065626):

Command:      codex
Path:         /Applications/ChatGPT.app/Contents/Resources/codex
Parent:       UNKNOWN [98060]
PID:          98180
Event:        disk writes
Action taken: none
Writes:       34.36 GB of file backed memory dirtied over 23191 seconds
              (1481.57 KB per second average),
              exceeding limit of 397.68 KB per second over 86400 seconds
Writes limit: 34.36 GB
Free disk space: 6689.84 MB / 460.43 GB, low space threshold 3072 MB

The write path — SQLite WAL checkpoint

The heaviest stack for the terminated process points squarely at SQLite checkpointing (the binary is Rust; symbols are partial but the SQLite frames are clear):

_pthread_start
  std::sys::backtrace::__rust_begin_short_backtrace
    sqlite3_exec
      sqlite3_step
        sqlite3_wal_checkpoint_v2
          ??? (codex)
            pwrite   ← libsystem_kernel

The write target — unbounded local logging DB

On disk, the logging database is enormous and actively growing while the app runs:

2.6–2.8 GB   ~/.codex/logs_2.sqlite         (+ ~40 MB  logs_2.sqlite-wal, live)
8.7 GB       ~/.codex/sessions
271 MB       ~/.codex/sqlite
13 GB        ~/.codex   (total)

A multi-GB SQLite DB under continuous WAL checkpointing memory-maps and re-dirties a large working set on every checkpoint; macOS accounts those dirtied file-backed pages against the process's disk-write highwater limit, so the process is killed on a timer regardless of available RAM. (At the time I write this, free RAM is 81%, yet the process was still killed — this is a write-rate, not a memory-exhaustion, limit.)

Recurrence & escalation (last 9 events)

The macOS monitor re-arms with a doubling highwater after each kill, so the "Writes limit" climbs 2 GB → 8 GB → 34 GB across successive terminations. Free disk space is drawn down in parallel.

Local time Command PID / Parent Writes limit tripped Free disk Bundle
2026-07-11 06:56 codex 98180 / 98060 34.36 GB 6.69 GB ChatGPT.app
2026-07-11 00:29 ChatGPT 98060 / 1 8.59 GB 42.38 GB ChatGPT.app
2026-07-10 21:48 codex 98180 / 98060 2.15 GB 14.13 GB ChatGPT.app
2026-07-09 21:14 ChatGPT 45816 / 1 2.15 GB 11.46 GB ChatGPT.app
2026-07-09 07:59 codex 42626 / 42316 8.59 GB 15.79 GB Codex.app
2026-07-09 05:32 codex 19673 / 19614 2.15 GB 25.17 GB Codex.app
2026-07-06 21:36 codex 88485 / 88385 2.15 GB — Codex.app
2026-07-06 19:56 codex 88485 / 88385 2.15 GB — Codex.app
2026-07-05 22:50 codex 33712 / 33537 2.15 GB 14.88 GB Codex.app

Note the parent/child pairing: the main ChatGPT/Codex process (parent PID 1) and its long-lived codex helper (Contents/Resources/codex) are both killed under the same signature. In the latest event the helper's parent (98060) is exactly the main process that was killed 6.4 h earlier — so the helper kept writing after its parent died.


Defect B — unclean kill blocks relaunch (stale singleton + orphaned helpers)

After the resource-limit kill, the app would not reopen (open -a ChatGPT returns 0 but no process appears). Two leftovers were responsible:

1. Stale Chromium singleton lock in ~/Library/Application Support/Codex/:

SingletonLock   -> ZimingWangs-MacBook-Pro-4.local-98060   # PID 98060 no longer exists
SingletonCookie -> 10769481914328928945
SingletonSocket -> /var/folders/.../T/com.openai.codex.fmOZMW/SingletonSocket

98060 was the PID killed at 00:29; the lock was never released, and the new instance would not take over the stale lock.

2. Orphaned helper processes (parent reparented to PID 1) that survived the kill:

PID    PPID  ELAPSED    COMM
17301  1     23:21:47   .../Codex Framework.framework/Versions/150.0.7871.101/Helpers/browser_crashpad_handler
17303  1     23:21:47   .../browser_crashpad_handler
29616  1     03:57:55   .../ChatGPT.app/Contents/Resources/native/bare-modifier-monitor

These handlers persisted for ~23 h after their parent died, still referencing the previous framework version (150.0.7871.101) even though the on-disk framework had already updated to .115.

Manual recovery that worked

# 1. reap the orphaned helpers (all had PPID==1)
kill 17301 17303 29616

# 2. remove the stale singleton artifacts (lock holder PID confirmed dead)
cd ~/Library/Application\ Support/Codex
rm -f SingletonLock SingletonCookie SingletonSocket

# 3. relaunch
open -a ChatGPT

The app then started normally and recreated a healthy SingletonLock -> ...-<live PID>. Full Chromium process tree (main + services + renderers) came up; no new crash report was generated.


Suggested fixes

Defect A (root cause):

  • Bound logs_2.sqlite: enforce a size/row cap with periodic pruning or rotation; a local log DB should not be allowed to reach multiple GB.
  • Avoid full-WAL-checkpoint storms: use wal_autocheckpoint tuning / PRAGMA journal_size_limit, or TRUNCATE-mode checkpoints on a bounded cadence rather than continuous wal_checkpoint_v2 under a huge WAL.
  • Consider throttling log-write volume at the source; 34 GB dirtied in 6.4 h from a logging path is disproportionate.
  • Also bound ~/.codex/sessions (8.7 GB here) with retention/archival limits.

Defect B (resilience):

  • On startup, detect and clear a SingletonLock whose recorded host matches the local host but whose PID is dead (standard stale-lock recovery), instead of silently failing to launch.
  • Reap the app's own orphaned helpers (browser_crashpad_handler, bare-modifier-monitor) on next launch, or make the singleton takeover robust to their presence.

Relationship to existing issues

This report corroborates and sharpens several open issues, but adds the concrete OS disk writes termination signature and the SQLite-WAL write path, which I did not find documented:

If maintainers prefer, Defect A can fold into #26869 and Defect B can be filed separately as a launch-resilience bug.


Diagnostic commands (for reproduction / triage)

# OS-kill reports (this is the ground truth — look for "Event: disk writes")
ls -lt /Library/Logs/DiagnosticReports/*[Cc]odex* /Library/Logs/DiagnosticReports/*[Cc]hatGPT*
grep -E '^(Command|Event|Writes:|Writes limit|Parent|Free disk space):' <report>.diag

# write target sizes
du -sh ~/.codex ~/.codex/logs_2.sqlite ~/.codex/sessions

# stale singleton after an unclean kill
ls -la ~/Library/Application\ Support/Codex/Singleton*

# orphaned helpers (PPID==1) left behind
ps -eo pid,ppid,etime,comm | grep -Ei 'Codex Framework|bare-modifier-monitor'

Reporter environment captured 2026-07-11. Diagnostic reports available on request (Microstackshots disk writes .diag files, Jul 5–11, 2026).

Activity

  1. xiaogege6697 commented on Oct 6, 2026

    @xiaogege6697

    Same signature on a second machine — adding data points and the outcome after workaround + app update, in case it helps pin down the write loop.

    Environment

    • ChatGPT desktop 26.930.51102 (13100), macOS 26.3.2 (25D2140), Apple Silicon 8 GB RAM (Mac17,5)

    Defect A data points (this machine)

    • Main process (/Applications/ChatGPT.app/Contents/MacOS/ChatGPT, coalition com.openai.codex) hit the disk writes advisory twice in one day:
      • 34.36 GB dirtied over ~5 h (18,141 s), then
      • after a reboot: 34.52 GB over 489 s (70.63 MB/s average) — powerstats showed footprint going 1503 MB → 3222 MB in ~7.5 s, with 126 threads all blocked in write + 8 under uv_cancel (Codex Framework, libuv).
    • Two shutdown_stall events while the app hung in write() — the machine could not shut down cleanly.
    • After I archived away ~/.codex/archived_sessions (4.2 GB, largest single rollout jsonl 1.4 GB) + generated_images (1.3 GB) and VACUUMed the dbs, the write rate dropped an order of magnitude but was still ~7.5 MB/s sustained (8.59 GB over 1,144 s → a third advisory). Notably at that point thread_history_1.sqlite was 1.9 GB of live data (VACUUM did not shrink it) and logs_2.sqlite was only ~80 MB post-VACUUM — so on this machine the residual churn doesn't look like log-db bloat alone.

    Defect B confirmed too: found a stale SingletonLock/SingletonSocket/SingletonCookie in ~/Library/Application Support/Codex/ pointing at a dead PID; cleared manually. state_5.sqlite showed backfill_state = complete and rollout_migration_state = legacy_to_paginated_v1 still advancing, for what it's worth.

    Resolution on this machine: the app auto-updated to 26.930.61225, and with the cleaned data dir + lock removed, a 5-minute launch test shows a stable PID (no relaunch loop), RSS settling at 141–633 MB, and no new advisory. Happy to share the full .diag powerstats stacks if useful.

  2. xiaogege6697 commented on Oct 7, 2026

    @xiaogege6697

    Follow-up with a controlled A/B data point from the 8 GB machine:

    • After deleting the 546 imageGeneration rows (1.5 GB of base64 images) from thread_items (library shrunk 1.9 GB → 411 MB), the app ran for ~12 hours with zero new disk writes advisories.
    • After restoring the original 1.9 GB library this morning, the very first launch tripped a new advisory: 2,353.92 MB dirtied in 15 s (159.84 MB/s), footprint +1,765 MB in the same window, 126 threads in write(), Pageins only 300 pages (so not swap thrash).

    File-level evidence during the burst: mtimes advancing on logs_2.sqlite, state_5, queue_1, goals_1 and their WALs — those four DBs total only ~100 MB on disk, yet the dirty accounting was 2.3 GB in 15 s, i.e. the same pages are being re-dirtied in a loop rather than new data being persisted. thread_history_1.sqlite itself was untouched during the burst.

    So on this machine the burst correlates with image-bearing context being loaded at launch, and physically manifests as page-level re-dirtying across the small state/log DBs. Happy to run fs_usage capture if it would help.

  3. xiaogege6697 commented on Oct 7, 2026

    @xiaogege6697

    Root cause found on the 8 GB machine — the persistent disk writes bursts were NOT the log DB.

    Kernel-level fs_usage + lsof capture during a live burst (writes at 160–170 MB/s sustained, one single write() of 224 MB observed, ~18k constant 512 KB writes per 40 s, 4 worker threads each ~1 GB per launch) showed the top writers were:

    1. ~/.codex/.codex-global-state.json had grown to 214 MB (healthy size: ~33 KB). Every state change re-serializes and rewrites the whole file via the atomic tmp + rename pattern, several times per second → that alone accounts for the sustained 160+ MB/s dirty rate. The ..codex-global-state.json.tmp-<ts>-<uuid> sidecar files pile up (dozens per launch; 10 stale ones found dating back months).
    2. ~/.codex/automations/*/automation.toml (a 1.6 KB file) received 1.7 GB of writes in one 40 s window — same write-amplification pattern.

    Fix verified on this machine: back up the file, reset it to {}, launch the app — it rebuilds at 33 KB, first-launch real-time disk writes drop to <1 MB/s (vs 160–170 MB/s), zero new tmp files in 35 s.

    If others want to check: ls -lh ~/.codex/.codex-global-state.json — if it's tens/hundreds of MB, this unbounded growth of electron-persisted-atom-state and its full-file rewrite per change is very likely your advisory source too.

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 appbugSomething isn't workingperformance

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions