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

  1. Ensure only one Codex++ instance runs at a time (close other sessions/IDE windows using the same codex_home) and retry reconcile
  2. Wait for the plugin's own upgrade to finish, then re-run reconciliation so recovery sees a stable file
  3. 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

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


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(&current) == contract.service_sha,
            "Runtime changed outside Codex++"
        );
    }
    {
        let candidate = transform(&current, &control, contract)?;
        if backup.exists() {
            ensure!(

View on GitHub (pinned to b1ed92e5e4)