BigPizzaV3/CodexPlusPlus · error

Concurrent runtime change

Error message

Concurrent runtime change

What it means

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.

Solutions

  1. Ensure no other codex/desktop instance is running or updating while reconcile runs, then retry.
  2. Retry the operation — the race window is tiny and a rerun usually succeeds.
  3. Exclude the codex plugin cache directory from antivirus and file-sync tools.
  4. Verify reconcile is only called from the owning launcher while holding the `owner.lock` (the lock error would otherwise surface first).

Example fix

// before
match reconcile(&paths, true) { Err(e) if is_concurrent_change(&e) => { /* ignore */ } }
// after
// back off and retry with no concurrent writers
std::thread::sleep(RETRY_DELAY);
reconcile(&paths, true)?;
Defensive patterns

Strategy: retry

Validate before calling

// before calling, ensure no concurrent writers:
// check no other codex/desktop process is running and no pending plugin updates
assert_no_codex_processes()?; // e.g. pid-file / process check

Try / catch

match reconcile(&paths, true) {
    Err(e) if e.to_string().contains("Concurrent runtime change") => {
        std::thread::sleep(Duration::from_millis(250));
        reconcile(&paths, true)?; // single retry after quiescing writers
    }
    other => other?,
}

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/9f71a069c1cc53f5. Report an issue: GitHub.

Appendix: source

Thrown at crates/codex-plus-core/src/native_browser.rs:449

        let journal = Journal {
            schema: 1,
            original_sha: contract.service_sha.clone(),
            candidate_sha: sha(&candidate),
            modified_secs: modified.as_secs(),
            modified_nanos: modified.subsec_nanos(),
        };
        // Durable original and journal precede any runtime write.
        atomic_write(&journal_path, &serde_json::to_vec(&journal)?)?;
    }
    let (journal, original, candidate) = recovery_material(paths, key, contract)?;
    if current == candidate {
        return Ok(());
    }
    ensure!(
        current == original && sha(&current) == journal.original_sha,
        "Runtime changed outside Codex++; refusing to overwrite"
    );
    ensure!(
        read_regular(&target, MAX_SERVICE)? == current,
        "Concurrent runtime change"
    );
    atomic_write(&target, &candidate)?;
    ensure!(
        read_regular(&target, MAX_SERVICE)? == candidate,
        "Runtime write verification failed"
    );
    Ok(())
}

fn recovery_material(
    paths: &BrowserPaths,
    key: &str,
    contract: &RuntimeContract,
) -> Result<(Journal, Vec<u8>, Vec<u8>)> {
    ensure!(key_valid(key), "Invalid recovery key");
    let dir = paths.state_root.join(key);

View on GitHub (pinned to b1ed92e5e4)