{"record":{"id":"48c133450ccdfe75","repo":"BigPizzaV3/CodexPlusPlus","slug":"candidate-backup-conflict","errorCode":null,"errorMessage":"Candidate backup conflict","messagePattern":"Candidate backup conflict","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/native_browser.rs","lineNumber":421,"sourceCode":"        }\n        ensure!(\n            sha(&current) == contract.service_sha,\n            \"Runtime changed outside Codex++\"\n        );\n    }\n    {\n        let candidate = transform(&current, &control, contract)?;\n        if backup.exists() {\n            ensure!(\n                read_regular(&backup, MAX_SERVICE)? == current,\n                \"Unjournaled backup conflict\"\n            );\n        } else {\n            write_new(&backup, &current)?;\n        }\n        let candidate_path = backup_dir.join(format!(\"candidate-{}.mjs\", sha(&candidate)));\n        if candidate_path.exists() {\n            ensure!(\n                read_regular(&candidate_path, MAX_SERVICE)? == candidate,\n                \"Candidate backup conflict\"\n            );\n        } else {\n            write_new(&candidate_path, &candidate)?;\n        }\n        let modified = fs::metadata(&target)?\n            .modified()?\n            .duration_since(UNIX_EPOCH)?;\n        let journal = Journal {\n            schema: 1,\n            original_sha: contract.service_sha.clone(),\n            candidate_sha: sha(&candidate),\n            modified_secs: modified.as_secs(),\n            modified_nanos: modified.subsec_nanos(),\n        };\n        // Durable original and journal precede any runtime write.\n        atomic_write(&journal_path, &serde_json::to_vec(&journal)?)?;","sourceCodeStart":403,"sourceCodeEnd":439,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/native_browser.rs#L403-L439","documentation":"In `prepare`, the transformed candidate is stored as `state_root/<key>/candidate-<sha>.mjs`. If that file already exists, its content must equal the freshly computed candidate; a hash-named file with mismatching content means the candidate cache is corrupt or the hash naming invariant was violated (content no longer matches its own sha name). Codex++ aborts instead of silently reusing or overwriting a bad candidate.","triggerScenarios":"`reconcile(paths, true)` path where `candidate-<sha(&candidate)>.mjs` already exists in the backup dir and `read_regular(candidate_path) != candidate`. Requires the file content to diverge from the sha in its filename — e.g. partial write, manual edit, or a tool that rewrote the file in place.","commonSituations":"Manual editing or 'cleaning' of the state directory; a crashed/partial `write_new` followed by a disk tool recovering the wrong bytes; a shared/synced state dir (Dropbox etc.) merging conflicting versions.","solutions":["Delete the corrupt `candidate-<sha>.mjs` file and rerun reconcile so it is rewritten from the transform output.","Reset the entire `state_root/<key>` directory and rerun reconcile.","Exclude the Codex++ state directory from file-sync tools that can rewrite files in place.","Verify the file's sha256 actually differs from its filename; if it matches, the transform is non-deterministic — check `control.json`."],"exampleFix":"// before (shell)\n# editing the candidate file by hand\nvim ~/.codex/plugins/state/<key>/candidate-<sha>.mjs\n// after\n# never edit; delete and let reconcile regenerate\nrm ~/.codex/plugins/state/<key>/candidate-<sha>.mjs","handlingStrategy":"validation","validationCode":"let candidate_path = state_root.join(key).join(format!(\"candidate-{}.mjs\", expected_sha));\nif candidate_path.exists() {\n    let on_disk = std::fs::read(&candidate_path)?;\n    if sha256(&on_disk) != expected_sha { /* corrupt: delete the candidate file before reconcile */ }\n}","typeGuard":null,"tryCatchPattern":"match reconcile(&paths, true) {\n    Err(e) if e.to_string().contains(\"Candidate backup conflict\") => {\n        for entry in std::fs::read_dir(state_root.join(&key))?\n            .filter_map(Result::ok)\n            .filter(|e| e.file_name().to_string_lossy().starts_with(\"candidate-\"))\n        { let _ = std::fs::remove_file(entry.path()); }\n        reconcile(&paths, true)?;\n    }\n    other => other?,\n}","preventionTips":["Never manually edit files under the state root","Verify candidate files hash to their filename if inspecting state","Exclude state dirs from cloud-sync/backup restore tools","Treat state_root as opaque, owned by Codex++"],"tags":["file-conflict","checksum-mismatch","state-management"],"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"}