BigPizzaV3/CodexPlusPlus · error

rollout changed before rollback

Error message

rollout changed before rollback: {}

What it means

restore_bulk_session_rewrite refuses to overwrite a rollout file whose current SHA-256 no longer matches the `rewritten_sha256` recorded when the bulk rewrite was applied. This safety check prevents clobbering newer content with the stored pre-image, so the file is reported as 'changed before rollback'.

Solutions

  1. Stop the Codex client (or anything writing that rollout file) so files stop changing, then redo the rollback
  2. Restore that specific file from an external backup — the library intentionally will not overwrite diverged files
  3. If the current content is actually wanted, remove it from the pending changes list instead of forcing a restore
  4. Serialize undo/sync operations with a lock to prevent concurrent rewrites

Example fix

// before: running undo while codex is live -> file hash drifts after rewrite
$ codex-plus undo  # rollback of rollout.jsonl fails: changed before rollback
// after: stop codex first, then undo
$ codex quit && codex-plus undo
Defensive patterns

Strategy: validation

Validate before calling

let current = sha256_file(&change.plan.path)?;
if current != change.rewritten_sha256 {
    // file drifted; restore from external backup instead of library rollback
}

Try / catch

match restore_bulk_session_rewrite(change) {
    Err(e) if e.to_string().contains("changed before rollback") => {
        eprintln!("file drifted; use external backup for {}", change.plan.path.display());
    }
    other => other?,
}

Prevention

When it happens

Trigger: Between the bulk session rewrite and the rollback, the rollout file at change.plan.path was modified — by the Codex client appending events, by another sync run, or by manual editing — so sha256_file(path) != change.rewritten_sha256.

Common situations: Codex client still running and appending to session rollouts during restore; two sync/undo operations racing on the same session; user manually editing rollout JSON before undoing.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/bba8c3b6bafc1551. Report an issue: GitHub.

Appendix: source

Thrown at crates/codex-plus-data/src/provider_sync.rs:3668

    let mut restore_errors = Vec::new();
    for change in changes.iter().rev() {
        if let Err(error) = restore_bulk_session_rewrite(change) {
            restore_errors.push(format!("{}: {error:#}", change.plan.path.display()));
        }
    }
    if !restore_errors.is_empty() {
        return Err(anyhow::anyhow!(
            "failed to restore {} rollout file(s): {}",
            restore_errors.len(),
            restore_errors.join("; ")
        ));
    }
    Ok(())
}

fn restore_bulk_session_rewrite(change: &AppliedBulkSessionRewrite) -> anyhow::Result<()> {
    if sha256_file(&change.plan.path)? != change.rewritten_sha256 {
        return Err(anyhow::anyhow!(
            "rollout changed before rollback: {}",
            change.plan.path.display()
        ));
    }

    codex_plus_core::settings::atomic_write_with(&change.plan.path, |file| {
        let mut writer = HashingWriter::new(BufWriter::new(file));
        let source_sha256 = stream_restore_rollout_session_meta_lines(
            &change.plan.path,
            &change.plan.original_session_meta_lines,
            &mut writer,
        )?;
        writer.flush()?;
        if source_sha256 != change.rewritten_sha256
            || sha256_file(&change.plan.path)? != change.rewritten_sha256
        {
            return Err(std::io::Error::other(BULK_SESSION_SOURCE_CHANGED_ERROR));
        }

View on GitHub (pinned to b1ed92e5e4)