codex.exe exits with code 0 mid-session; thread-store then fails "expected ordinal N, got N-1"

Open 💬 5 comments Opened Aug 25, 2026 by polovik220-ux
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of the Codex App are you using (From “About Codex” dialog)?

OpenAI.Codex 26.818.8289.0; codex-core 0.149.0-alpha.4.3

What subscription do you have?

ChatGPT Pro ($200/month)

What platform is your computer?

Microsoft Windows NT 10.0.19045.0 x64 (Windows 10 build 19045)

What issue are you seeing?

Environment: OpenAI Codex desktop app (Microsoft Store), package OpenAI.Codex 26.818.8289.0, engine codex-core 0.149.0-alpha.4.3, Windows 10 build 19045.

During an active session, the codex.exe engine process periodically terminates with exit code 0x00000000. This is a clean exit: there is no Application Error or Windows Error Reporting event and Windows does not create a crash dump. The exit code was confirmed by attaching ProcDump to codex.exe.

After each exit, the thread whose rollout was being written becomes permanently broken. Every subsequent durable write fails:

codex_thread_store::local::live_writer ... failed to project durable rollout for <tid>:
thread-store internal error: thread history projection for <tid> expected ordinal N, got N-1

Subsequent turns then fail with:

Custom tool call output is missing for call id ...

The affected thread remains unusable. Archiving only stops additional errors because the archived thread is no longer written.

Observed scope from a read-only snapshot of logs_2.sqlite taken at 2026-08-25 15:50:27 UTC:

  • 6 distinct affected threads
  • 2,713 rows containing expected ordinal
  • First event: 2026-08-23 21:13:23 UTC
  • Most recent event: 2026-08-25 15:49:56 UTC

One exact logged failure:

handle_tool_call_with_source:dispatch_tool_call_with_code_mode_result{otel.name=apply_patch tool_name=apply_patch call_id="exec-f4c539e3-e06f-44a6-9ce4-b24617e7e213" aborted=false}:dispatch_tool_call_with_terminal_outcome:persist_rollout_items{item_count=1}:append_items{item_count=1}:append_items{item_count=1}: failed to project durable rollout for 01a02da6-8925-7412-a4b6-3bf9c0af6a98: thread-store internal error: thread history projection for 01a02da6-8925-7412-a4b6-3bf9c0af6a98 expected ordinal 6732, got 6731

This does not appear to be a network or authentication issue: backend requests return 200 OK and authentication remains valid. It also does not appear to be an OS-level crash.

A full user-mode dump captured at the exact exit moment is available on request. It may contain session data and is therefore not attached publicly.

This may be related to #40231, but the termination signature is different: that report shows 0xC000013A / STATUS_CONTROL_C_EXIT, while this report was externally observed as 0x00000000.

What steps can reproduce the bug?

  1. Use Codex Desktop normally in an active session.
  2. Continue a turn that performs local work while the rollout is being written.
  3. When the periodic codex.exe exit occurs mid-write, Codex Desktop restarts the engine.
  4. Resume or continue the affected thread.
  5. Durable writes repeatedly fail with the thread-history ordinal mismatch, followed by missing custom tool-call output errors.

The exit is intermittent, but every observed thread active at the exit becomes unusable afterward.

What is the expected behavior?

codex.exe should remain running during an active turn. If the process exits or is restarted, the durable thread history should recover atomically without an ordinal mismatch or a missing tool-call output, and the thread should remain usable.

Additional information

The SQLite log database was inspected through a read-only URI with query-only mode enabled. No Codex database was modified.

No public memory dump is attached. A full user-mode dump captured at the exit moment can be provided through a secure channel on request.

View original on GitHub ↗

5 Comments

github-actions[bot] contributor · 2 days ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #40400
  • #40607
  • #40537
  • #40231

Powered by Codex Action

Ash20pk · 2 days ago

Root-caused the second half of this (the thread-store failure after the restart). It's a resume bug, independent of why codex.exe exited, and it's the same defect behind #40537 on macOS, where no crash is involved at all.

Symptom → mechanism

expected ordinal N, got N-1 means SQLite's projection checkpoint has already consumed ordinal N-1, but the next durable JSONL line carries N-1 again — the recorder re-issued an ordinal. That can only happen if the recorder, on resume, believed the final record in the rollout was N-2.

There are two different decoders reading the same JSONL:

  • The projection (thread-store/src/local/thread_history_materialization.rs) parses each line as serde_json::Value and goes through codex_rollout::decode_rollout_line.
  • The recorder's resume path (rollout/src/ordinal.rs::ordinal_state_for_rollout) reverse-scans the tail with scanner.scan_next::<RolloutLine>() — a typed parse of a struct with #[serde(flatten)] item.

decode_rollout_line exists precisely because those disagree. Its own doc comment: "With serde_json/arbitrary_precision, Serde's generic buffer cannot replay floating-point values nested inside flattened or internally tagged fields." That feature is enabled workspace-wide through unification — codex-protocol → codex-execpolicy → starlark → serde_json/arbitrary_precision (and again via exec-server-protocol), so it's on in codex.exe, the CLI, and even cargo test -p codex-rollout.

So when the last complete line in the rollout contains a float, the typed scan Rejecteds it and steps back one record; the projection had accepted it. The recorder resumes at N-1, writes a valid duplicate-ordinal line, and apply_projection rejects it (ordinal != next_ordinal). Because the recorder keeps advancing from the wrong base, every later write also fails — hence 2,713 rows and "the thread is permanently broken."

The float that does it is token_countrate_limits.primary.used_percent (an f64, e.g. 12.5; 0.0 also breaks — any decimal). A token_count event is emitted after every model response, so it is very commonly the final record whenever the process dies between or during turns, and it's the last record when a fork is prepared from an idle thread (#40537).

Reproduction

Added a unit test in codex-rs/rollout/src/recorder_tests.rs: write a paginated rollout [meta@0, agent_message@1, token_count{used_percent: 12.5}@2], then append_rollout_item_to_path. Expected ordinals [0,1,2,3].

test recorder::tests::append_after_float_rate_limit_record_does_not_reuse_its_ordinal ... FAILED
  left:  [Some(0), Some(1), Some(2), Some(2)]     <- ordinal 2 reissued
  right: [Some(0), Some(1), Some(2), Some(3)]

Plain cargo test -p codex-rollout on today's main (304c8de) — no special flags needed, since arbitrary_precision is already unified into the crate's test build.

Fix that makes the test pass

Use the same decoder on the write side that the projection uses on the read side:

  • rollout/src/ordinal.rs: scan_next::<serde_json::Value>() + decode_rollout_line in ordinal_state_for_rollout, and the same for the first-line parse in read_history_metadata.
  • thread-store/src/local/model_context.rs: same change to the reverse scan that rebuilds model context — it currently silently drops float-bearing records for the same reason.

With those two changes: the new test passes, cargo test -p codex-rollout is 124/124, cargo test -p codex-thread-store passes, cargo clippy --tests -D warnings is clean. I know external PRs aren't accepted; the branch is here if it's useful to pull from: https://github.com/Ash20pk/codex/commit/01a6fd3e26fe50591b05336d34ca5477bf276e59 (branch fix/rollout-ordinal-resume-float-records)

Two things the fix does not do

  1. Already-damaged rollouts. Affected JSONL files now contain two complete records with the same ordinal. Re-projecting from offset 0 would hit the same expected N, got N-1 at the duplicate, so those threads need a one-off repair (renumber from the first duplicate onward, then drop the thread's thread_history_projection_state row so it re-materializes). That's a maintainer call.
  2. The exit itself. Clean exit-0 mid-turn is a separate bug; this just means a restart stops corrupting the thread that was live.

Other typed RolloutLine parses with the same latent issue, lower stakes because they degrade rather than corrupt: rollout/src/list.rs:1231,1288 (read_head_for_summary skips the line) and rollout/src/search.rs:251 (content search misses it).

Carvalho3009 · 2 days ago

Codex Desktop: paginated history becomes permanently truncated after interrupted turn/resume

Submission target

Independent Windows confirmation for:

  • openai/codex#40630 — codex.exe exits with code 0 mid-session; thread-store then fails "expected ordinal N, got N-1"
  • Closely related root-cause tracking: openai/codex#35746

Environment

  • Platform: Windows 11 Professional x64 (10.0.26200)
  • Current Codex Desktop: 26.820.7780.0
  • Current bundled runtime: codex-cli 0.150.0-alpha.8
  • Build visible when the latest affected boundary was resumed: 26.818.61809
  • Runtimes recorded when the affected threads were created:
  • 0.149.0-alpha.4.1
  • 0.149.0-alpha.4.3
  • History mode: paginated

Actual behavior

Three consecutive project threads developed the same failure. After a turn was interrupted without a durable task_complete, later turns continued to be written to the canonical rollout JSONL. After reopening or resuming the thread, the Desktop UI displayed only the older projected portion. Newer user messages, assistant messages, tool activity, and completed turns remained present in the rollout but were absent from thread/read and the conversation pane.

Each affected projection contains one stale inProgress turn. The Desktop resume log recognizes the latest turn as interrupted, but the durable projection is not reconciled.

Every subsequent write repeatedly logs:

failed to project durable rollout for <redacted>:
thread-store internal error: thread history projection for <redacted>
expected ordinal N, got N-1

Sanitized evidence

| Case | Runtime at creation | Projection expects | Boundary record | Boundary ordinal | Following record | Hidden rollout suffix | Projection warnings |
|---|---|---:|---|---:|---|---:|---:|
| 1 | 0.149.0-alpha.4.1 | 1002 | thread_settings_applied | 1001 | task_started(1002) | 104.3 MiB | 361 |
| 2 | 0.149.0-alpha.4.3 | 3198 | thread_settings_applied | 3197 | task_started(3198) | 10.9 MiB | 357 |
| 3 | 0.149.0-alpha.4.3 | 4490 | thread_settings_applied | 4489 | task_started(4490) | 9.3 MiB and growing | 378 |

In every case, the physical record immediately before the projection boundary is event_msg/token_count with the same ordinal later reused by event_msg/thread_settings_applied:

token_count(N-1)
thread_settings_applied(N-1)  <-- duplicated ordinal after resume
task_started(N)

This leaves the projection checkpoint expecting N, while the next unprojected durable record is the replayed N-1.

The raw JSONL files parse successfully and retain the omitted conversation. SQLite integrity checks pass. codex doctor --json reports both the state database and thread-history database as intact. codex migrate-rollouts --thread <id> --json reports already_paginated and processes zero bytes, so it does not repair the projection.

Updating and restarting Codex Desktop did not repair the three already-affected threads.

Reproduction sequence

  1. Use a local Codex Desktop thread with paginated history.
  2. Let a turn end abruptly without task_complete or turn_aborted, leaving a trailing token_count record.
  3. Reopen/resume the thread.
  4. Send an ordinary follow-up.
  5. The resume writer reuses the trailing token_count ordinal for thread_settings_applied.
  6. Continue with additional turns; they complete and remain in the rollout.
  7. Reload or reopen the task.
  8. The UI remains pinned to the old projection and omits all later turns.

Expected behavior

  • Resume must allocate a strictly increasing ordinal after every valid durable record.
  • An interrupted turn must be durably reconciled as interrupted instead of remaining inProgress indefinitely.
  • The projector should automatically rebuild or repair a stale derived index from the canonical rollout.
  • If recovery fails, Desktop should surface a synchronization error instead of silently showing a plausible but truncated conversation.
  • Existing affected threads need a supported recovery path; preventing new duplicate ordinals alone will not restore them.

Privacy

No prompts, transcript text, repository names, local filesystem paths, account identifiers, credentials, raw databases, raw logs, or complete thread IDs are included. Complete local evidence can be retained for a private diagnostic channel if requested by OpenAI.

guilhermezatta · 2 days ago

Additional Windows data point for the same failure family, with an OS-level crash signature.

Environment

  • Codex Desktop package: 26.820.7780.0
  • Bundled Codex CLI: 0.150.0-alpha.8
  • Windows 10 x64, build 19045
  • Native Windows / PowerShell agent

Crash and timing

On 2026-08-26 at 00:54:34 local time, Windows Error Reporting recorded an actual codex.exe crash:

  • Event: BEX64
  • Exception code: 0xc0000409
  • Exception data: 0x7
  • Faulting module: codex.exe
  • The same process had logged normally until 00:54:31.
  • Crashpad recorded a renderer crash at 00:54:35, one second after the engine crash.

At the time of the crash, that engine process was actively servicing a resumed archived thread with a 220.7 MB durable rollout. Backend requests immediately before the crash returned 200 OK.

After the app restarted, the affected thread emitted 184 repeated projection failures over about 10 minutes:

failed to project durable rollout for <redacted-thread-id>:
thread-store internal error: thread history projection for <redacted-thread-id>
expected ordinal 9943, got 9942

A second archived thread with a 631.1 MB rollout had previously emitted repeated thread-store conflict: ... already has an active writer errors and retained three non-completed turn rows.

Local integrity checks

PRAGMA quick_check returned ok for:

  • logs_2.sqlite
  • thread_history_1.sqlite
  • state_5.sqlite
  • queue_1.sqlite

This supports a logical projection/index inconsistency rather than general SQLite corruption.

Recovery applied

I created a consistent SQLite backup, verified it with quick_check and SHA-256, then removed only the derived projection rows for the two affected thread IDs from:

  • thread_history_projection_state
  • thread_items
  • thread_turns

The durable rollout JSONL files were not modified or deleted. The transaction completed successfully, the live database still passes quick_check, and no new crash or Windows Application Hang event appeared afterward. Full re-projection on a later resume/restart remains to be observed because intentionally reopening the 220 MB / 631 MB threads during diagnosis could re-trigger the UI stall.

Resource context

At diagnosis time:

  • Codex profile: approximately 2.65 GB
  • Archived rollouts: approximately 1.39 GB
  • Codex processes: approximately 1.56 GB working set
  • System RAM: 5.9 GB total

This may amplify UI stalls, but the ordinal mismatch and the same-second engine/renderer crash are the stronger evidence. Raw transcripts, private paths, thread IDs, and dumps are intentionally not attached; more diagnostics can be provided through a secure channel if needed.

cooliang101 · 5 hours ago

Independent Windows confirmation: Desktop process replacement reused a token_count ordinal for task_started

I reproduced the same failure family on a local paginated Codex Desktop thread. This adds a process-replacement trigger and a boundary variant where the duplicated record is task_started rather than thread_settings_applied.

Environment
  • Windows 11 x64, build 22631
  • Currently installed Codex Desktop package: 26.825.3734.0
  • Affected transition recorded Desktop client versions changing from 26.820.60940 to 26.820.71523
  • App-server process changed from one PID/process UUID to another with a 61-second gap while an automatic Goal continuation was active
  • History mode: paginated
Durable boundary

The interrupted Goal turn had no task_complete or turn_aborted. Its final durable record was:

2026-08-27T06:42:29.012Z
ordinal 4726: event_msg/token_count
rate_limits.primary.used_percent = 44.0

After the Desktop/app-server process was replaced, the next continuation began with:

2026-08-27T06:43:25.598Z
ordinal 4726: event_msg/task_started   <-- duplicated ordinal
ordinal 4727: next response_item

The stored projection checkpoint remained:

next_rollout_byte_offset = 22,214,276
next_rollout_ordinal = 4727

The record at that byte offset is the duplicated ordinal-4726 task_started. Every later projection therefore fails with:

failed to project durable rollout:
thread history projection expected ordinal 4727, got 4726

I counted 365 occurrences of this exact warning for the affected thread.

User-visible impact
  • The canonical rollout remains valid JSONL and continues receiving complete later turns.
  • The rollout grew to 50,440,681 bytes, leaving about 28.2 MB after the frozen projection boundary.
  • The SQLite projection exposes only 44 older turns and retains one stale inProgress turn.
  • The Desktop conversation pane stops at the last completed turn before that stale turn.
  • New prompts and completed answers appear during the live run but disappear after reopening/restarting the app.
  • Restarting the current Desktop version does not repair the projection.
  • PRAGMA quick_check returns ok for state_5.sqlite, thread_history_1.sqlite, and logs_2.sqlite.

This supports the root cause already described here: reverse ordinal discovery skips the valid floating-point token_count tail, resumes from N-1, and permanently wedges the derived projection. The new detail is that an automatic Desktop/app-server replacement during a Goal continuation can trigger it, and newer resume paths may reuse the ordinal directly for task_started without an intervening thread_settings_applied record.

No transcript text, repository name, credentials, local paths, raw databases, or complete thread ID are included. I can retain the full local evidence for a private diagnostic channel if needed.