Bug: Rollout persistence errors silently discarded during session resume/fork — silent data loss on restart

Open
#35,385 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
68/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust
Domain
backend, cli

Research direction

Start in core/src/session/mod.rs at the resume and fork flush_rollout call sites around lines 1350 and 1397, then trace persist_rollout_items around lines 3610-3616 and its listed callers. Decide how persistence failures should reach the user or callers, and verify that resume, fork, event, response, compaction, and seeding paths no longer silently discard errors.

Written by the indexing model from the issue text.

Description

bug CLI session
What version of Codex CLI is running?

Reproduced with codex-cli 0.145.0. The same implementation is still present in main at e4fb5311d7468839def62eabda4b268f4a54cf11.

What platform is your computer?

Darwin 26.5.2 arm64 arm (macOS).

What issue are you seeing?

Session rollout persistence errors are silently discarded in three critical paths, causing in-memory state to diverge from on-disk state. If a crash or unclean shutdown occurs after the divergence, the session resumes from a stale or incomplete rollout, losing conversation history.

Three affected call sites:

1. flush_rollout() during session resume (core/src/session/mod.rs:1350)

// After InitialHistory::Resumed path
if !is_subagent {
    let _ = self.flush_rollout().await;  // io::Result discarded
}

2. flush_rollout() during session fork (core/src/session/mod.rs:1397)

// After InitialHistory::Forked path
if !is_subagent {
    let _ = self.flush_rollout().await;  // io::Result discarded
}

3. persist_rollout_items() throughout session lifecycle (core/src/session/mod.rs:3610-3616)

pub(crate) async fn persist_rollout_items(&self, items: &[RolloutItem]) {
    if let Some(live_thread) = self.live_thread()
        && let Err(e) = live_thread.append_items(items).await
    {
        error!("failed to record rollout items: {e:#}");  // logged, never propagated
    }
}

persist_rollout_items is called from 6+ critical paths:

  • send_event_raw_with_persistence (line 2076) — event messages
  • persist_rollout_response_items (line 3242) — response items
  • Compaction flow (line 3227) — history replacement
  • Session fork seeding (line 1390)

Root cause: flush_rollout() returns std::io::Result<()> and is the durability barrier after reconstructing session history. The let _ = pattern discards any I/O error (disk full, permissions error, network storage timeout). persist_rollout_items calls live_thread.append_items() which also returns a Result, but the error is only logged at error! level and never propagated to callers.

Impact: After any of these failures:

  1. In-memory history contains the full session state
  2. On-disk rollout is missing the failed items
  3. Session appears to operate normally (no error shown to user)
  4. On crash/restart, session resumes from the incomplete on-disk rollout
  5. Conversation history is silently truncated — recent messages, tool calls, and compaction summaries are lost

Severity: Medium-High. The failure requires an I/O error during persistence (disk full, NFS timeout, permissions issue), but the consequence is silent data loss that the user cannot detect or recover from.

What steps can reproduce the bug?
  1. Fill the disk to near capacity (e.g., dd if=/dev/zero of=filler bs=1M count=900)
  2. Start a Codex CLI session and have a conversation
  3. Resume or fork the session (the flush_rollout path)
  4. The flush_rollout call fails with io::ErrorKind::Other or io::ErrorKind::StorageFull
  5. The error is discarded — session appears normal
  6. Kill the process (Ctrl+C or crash)
  7. Resume the session — history is truncated to the point before the failed flush
What is the expected behavior?

Rollout persistence errors should be surfaced to the user (at minimum a warning notification) and the session should indicate that durability was not achieved. In the case of flush_rollout, the error should be propagated so the caller can decide whether to retry or warn the user.

Suggested fix

Option A (minimal): Log at error! level and notify the user via AppEvent::Warning:

// mod.rs:1350 and 1397
if let Err(e) = self.flush_rollout().await {
    error!("failed to flush rollout during resume: {e:#}");
    self.send_event_raw(AppEvent::Warning(format!(
        "Session history may not be fully saved: {e}"
    )));
}

Option B (comprehensive): Change persist_rollout_items to return Result and propagate to callers:

pub(crate) async fn persist_rollout_items(&self, items: &[RolloutItem]) -> anyhow::Result<()> {
    if let Some(live_thread) = self.live_thread() {
        live_thread.append_items(items).await?;
    }
    Ok(())
}
Related
  • #31074 — stale session_index entries resolving to missing rollout files (same persistence layer)
  • #34282 — rollout trace reducer panics on non-ASCII truncation (same persistence layer)
  • #34935 — orphan threads that disappear after restart (potential cascade from persistence failures)
Scope

Three call sites in one file (core/src/session/mod.rs). The fix is 5-15 lines depending on option chosen. No behavioral change to正常 operation — only error handling for the failure path.

Dominant language
Rust
Stars
125k
Forks
19.5k
Avg merge
1m
Merged PRs (30d)
1k

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from openai/codex

All issues in openai/codex

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.