{"record":{"id":"bba8c3b6bafc1551","repo":"BigPizzaV3/CodexPlusPlus","slug":"rollout-changed-before-rollback","errorCode":null,"errorMessage":"rollout changed before rollback: {}","messagePattern":"rollout changed before rollback: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-data/src/provider_sync.rs","lineNumber":3668,"sourceCode":"    let mut restore_errors = Vec::new();\n    for change in changes.iter().rev() {\n        if let Err(error) = restore_bulk_session_rewrite(change) {\n            restore_errors.push(format!(\"{}: {error:#}\", change.plan.path.display()));\n        }\n    }\n    if !restore_errors.is_empty() {\n        return Err(anyhow::anyhow!(\n            \"failed to restore {} rollout file(s): {}\",\n            restore_errors.len(),\n            restore_errors.join(\"; \")\n        ));\n    }\n    Ok(())\n}\n\nfn restore_bulk_session_rewrite(change: &AppliedBulkSessionRewrite) -> anyhow::Result<()> {\n    if sha256_file(&change.plan.path)? != change.rewritten_sha256 {\n        return Err(anyhow::anyhow!(\n            \"rollout changed before rollback: {}\",\n            change.plan.path.display()\n        ));\n    }\n\n    codex_plus_core::settings::atomic_write_with(&change.plan.path, |file| {\n        let mut writer = HashingWriter::new(BufWriter::new(file));\n        let source_sha256 = stream_restore_rollout_session_meta_lines(\n            &change.plan.path,\n            &change.plan.original_session_meta_lines,\n            &mut writer,\n        )?;\n        writer.flush()?;\n        if source_sha256 != change.rewritten_sha256\n            || sha256_file(&change.plan.path)? != change.rewritten_sha256\n        {\n            return Err(std::io::Error::other(BULK_SESSION_SOURCE_CHANGED_ERROR));\n        }","sourceCodeStart":3650,"sourceCodeEnd":3686,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-data/src/provider_sync.rs#L3650-L3686","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Stop the Codex client (or anything writing that rollout file) so files stop changing, then redo the rollback","Restore that specific file from an external backup — the library intentionally will not overwrite diverged files","If the current content is actually wanted, remove it from the pending changes list instead of forcing a restore","Serialize undo/sync operations with a lock to prevent concurrent rewrites"],"exampleFix":"// before: running undo while codex is live -> file hash drifts after rewrite\n$ codex-plus undo  # rollback of rollout.jsonl fails: changed before rollback\n// after: stop codex first, then undo\n$ codex quit && codex-plus undo","handlingStrategy":"validation","validationCode":"let current = sha256_file(&change.plan.path)?;\nif current != change.rewritten_sha256 {\n    // file drifted; restore from external backup instead of library rollback\n}","typeGuard":null,"tryCatchPattern":"match restore_bulk_session_rewrite(change) {\n    Err(e) if e.to_string().contains(\"changed before rollback\") => {\n        eprintln!(\"file drifted; use external backup for {}\", change.plan.path.display());\n    }\n    other => other?,\n}","preventionTips":["Never edit rollout files between apply and rollback","Stop the Codex client so it stops appending to session rollouts","Use a lock to serialize undo/sync against other operations"],"tags":["rust","checksum","concurrency"],"backgroundTag":"checksum-mismatch","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}