BigPizzaV3/CodexPlusPlus · error
Concurrent adapter upgrade
Error message
Concurrent adapter upgrade
What it means
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.
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
Defensive patterns
Strategy: retry
Validate before calling
let before = sha(&read_regular(&target, MAX_SERVICE)?);
// ... after other work:
if sha(&read_regular(&target, MAX_SERVICE)?) != before { eprintln!("target changed mid-operation; another writer is active"); } Try / catch
for attempt in 0..3 {
match prepare(&paths, &key, &contract) {
Err(e) if e.to_string().contains("Concurrent adapter upgrade") => {
std::thread::sleep(Duration::from_secs(2)); // another process is patching; wait and retry
}
other => { other?; break; }
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Concurrent runtime change
- .codex-global-state.json changed while deleting thread
- Concurrent recovery change
- File grew beyond the size limit
- global state changed while provider sync was being written
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/1d829098b56fe1e6.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/native_browser.rs:394
);
}
let mut current = read_regular(&target, MAX_SERVICE)?;
let backup_dir = paths.state_root.join(key);
plain_path(&backup_dir)?;
fs::create_dir_all(&backup_dir)?;
let _backup_guards = pin_parents(&backup_dir.join("journal.json"))?;
let backup = backup_dir.join("original.mjs");
let journal_path = backup_dir.join("journal.json");
let control = paths.state_root.join("control.json");
if journal_path.exists() {
let (journal, original, recorded_candidate) = recovery_material(paths, key, contract)?;
let candidate = transform(&original, &control, contract)?;
if current == recorded_candidate {
if candidate == recorded_candidate {
return Ok(());
}
// Restore before upgrading the journal, so either journal can recover a crash.
ensure!(
read_regular(&target, MAX_SERVICE)? == current,
"Concurrent adapter upgrade"
);
let modified = UNIX_EPOCH
.checked_add(Duration::new(journal.modified_secs, journal.modified_nanos))
.context("Invalid recovery timestamp")?;
atomic_write_with_modified(&target, &original, Some(modified))?;
current = original;
}
ensure!(
sha(¤t) == contract.service_sha,
"Runtime changed outside Codex++"
);
}
{
let candidate = transform(¤t, &control, contract)?;
if backup.exists() {
ensure!(View on GitHub (pinned to b1ed92e5e4)