{"record":{"id":"1d829098b56fe1e6","repo":"BigPizzaV3/CodexPlusPlus","slug":"concurrent-adapter-upgrade","errorCode":null,"errorMessage":"Concurrent adapter upgrade","messagePattern":"Concurrent adapter upgrade","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/native_browser.rs","lineNumber":394,"sourceCode":"        );\n    }\n    let mut current = read_regular(&target, MAX_SERVICE)?;\n    let backup_dir = paths.state_root.join(key);\n    plain_path(&backup_dir)?;\n    fs::create_dir_all(&backup_dir)?;\n    let _backup_guards = pin_parents(&backup_dir.join(\"journal.json\"))?;\n    let backup = backup_dir.join(\"original.mjs\");\n    let journal_path = backup_dir.join(\"journal.json\");\n    let control = paths.state_root.join(\"control.json\");\n    if journal_path.exists() {\n        let (journal, original, recorded_candidate) = recovery_material(paths, key, contract)?;\n        let candidate = transform(&original, &control, contract)?;\n        if current == recorded_candidate {\n            if candidate == recorded_candidate {\n                return Ok(());\n            }\n            // Restore before upgrading the journal, so either journal can recover a crash.\n            ensure!(\n                read_regular(&target, MAX_SERVICE)? == current,\n                \"Concurrent adapter upgrade\"\n            );\n            let modified = UNIX_EPOCH\n                .checked_add(Duration::new(journal.modified_secs, journal.modified_nanos))\n                .context(\"Invalid recovery timestamp\")?;\n            atomic_write_with_modified(&target, &original, Some(modified))?;\n            current = original;\n        }\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!(","sourceCodeStart":376,"sourceCodeEnd":412,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/native_browser.rs#L376-L412","documentation":"During journal-based recovery, prepare() recomputes the patched candidate and compares it with what is recorded. If the on-disk target file changed between the initial read (current) and the re-read just before restoring the original, another process is concurrently upgrading/patching the same adapter. To avoid clobbering a competing writer, preparation aborts.","triggerScenarios":"prepare() with an existing journal: current == recorded_candidate, but the freshly computed candidate differs (contract changed), and the guard re-read of the target file no longer equals current — i.e. another Codex++ process (or the plugin itself) rewrote the service file mid-recovery.","commonSituations":"Two codex sessions reconciling the browser runtime simultaneously; the plugin's own updater patching the adapter while Codex++ recovers from a crash; a sync tool rewriting the file during recovery.","solutions":["Ensure only one Codex++ instance runs at a time (close other sessions/IDE windows using the same codex_home) and retry reconcile","Wait for the plugin's own upgrade to finish, then re-run reconciliation so recovery sees a stable file","Disable file-sync/backup tools on the codex_home runtime directories during upgrades"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"let before = sha(&read_regular(&target, MAX_SERVICE)?);\n// ... after other work:\nif sha(&read_regular(&target, MAX_SERVICE)?) != before { eprintln!(\"target changed mid-operation; another writer is active\"); }","typeGuard":null,"tryCatchPattern":"for attempt in 0..3 {\n    match prepare(&paths, &key, &contract) {\n        Err(e) if e.to_string().contains(\"Concurrent adapter upgrade\") => {\n            std::thread::sleep(Duration::from_secs(2)); // another process is patching; wait and retry\n        }\n        other => { other?; break; }\n    }\n}","preventionTips":["Run only one Codex++ instance against a given codex_home","Use a file lock around reconcile operations in multi-process setups","Pause plugin auto-upgrade and sync tools while recovering the adapter"],"tags":["concurrency","native-browser","race-condition","file-write"],"backgroundTag":"concurrent-modification-conflict","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"}