{"record":{"id":"9f71a069c1cc53f5","repo":"BigPizzaV3/CodexPlusPlus","slug":"concurrent-runtime-change","errorCode":null,"errorMessage":"Concurrent runtime change","messagePattern":"Concurrent runtime change","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/native_browser.rs","lineNumber":449,"sourceCode":"        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)?)?;\n    }\n    let (journal, original, candidate) = recovery_material(paths, key, contract)?;\n    if current == candidate {\n        return Ok(());\n    }\n    ensure!(\n        current == original && sha(&current) == journal.original_sha,\n        \"Runtime changed outside Codex++; refusing to overwrite\"\n    );\n    ensure!(\n        read_regular(&target, MAX_SERVICE)? == current,\n        \"Concurrent runtime change\"\n    );\n    atomic_write(&target, &candidate)?;\n    ensure!(\n        read_regular(&target, MAX_SERVICE)? == candidate,\n        \"Runtime write verification failed\"\n    );\n    Ok(())\n}\n\nfn recovery_material(\n    paths: &BrowserPaths,\n    key: &str,\n    contract: &RuntimeContract,\n) -> Result<(Journal, Vec<u8>, Vec<u8>)> {\n    ensure!(key_valid(key), \"Invalid recovery key\");\n    let dir = paths.state_root.join(key);","sourceCodeStart":431,"sourceCodeEnd":467,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/native_browser.rs#L431-L467","documentation":"Immediately before the atomic candidate write, `prepare` re-reads the target service file and requires it to still equal the `current` bytes validated a moment earlier. If it changed in between, another process (codex itself, antivirus, a sync tool) is concurrently mutating the runtime cache; proceeding would race, so Codex++ aborts with this error.","triggerScenarios":"`reconcile(paths, true)` where `read_regular(&target) != current` at the TOCTOU re-check just before `atomic_write(&target, &candidate)` — i.e. the file changed between the earlier read and this check.","commonSituations":"Two Codex++/codex processes patching the same plugin cache (lock held by another instance would normally prevent this, but direct codex updates bypass the lock); antivirus quarantine/rewrite; cloud-drive sync writing during reconcile.","solutions":["Ensure no other codex/desktop instance is running or updating while reconcile runs, then retry.","Retry the operation — the race window is tiny and a rerun usually succeeds.","Exclude the codex plugin cache directory from antivirus and file-sync tools.","Verify reconcile is only called from the owning launcher while holding the `owner.lock` (the lock error would otherwise surface first)."],"exampleFix":"// before\nmatch reconcile(&paths, true) { Err(e) if is_concurrent_change(&e) => { /* ignore */ } }\n// after\n// back off and retry with no concurrent writers\nstd::thread::sleep(RETRY_DELAY);\nreconcile(&paths, true)?;","handlingStrategy":"retry","validationCode":"// before calling, ensure no concurrent writers:\n// check no other codex/desktop process is running and no pending plugin updates\nassert_no_codex_processes()?; // e.g. pid-file / process check","typeGuard":null,"tryCatchPattern":"match reconcile(&paths, true) {\n    Err(e) if e.to_string().contains(\"Concurrent runtime change\") => {\n        std::thread::sleep(Duration::from_millis(250));\n        reconcile(&paths, true)?; // single retry after quiescing writers\n    }\n    other => other?,\n}","preventionTips":["Run only one codex/launcher instance during reconcile","Pause antivirus and file-sync for the codex directories","Serialize all reconcile calls through the owning launcher","Retry with backoff; the race window is tiny"],"tags":["race-condition","concurrency","file-write"],"backgroundTag":"concurrent-modification","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"}