BigPizzaV3/CodexPlusPlus · error

Backups must be outside the cache

Error message

Backups must be outside the cache

What it means

reconc_contract validates the caller-supplied BrowserPaths before doing any work: the backup/state directory (state_root) must not be located inside the runtime cache directory (runtime_root). If the backups lived inside the cache, restoring the service or the desktop app clearing the cache could destroy or resurrect the backup material itself. The library therefore refuses such a configuration up front with this error.

Solutions

  1. Move state_root (the backup/state directory) outside runtime_root — e.g. a sibling directory or an app-data location — and construct BrowserPaths with the corrected paths.
  2. If state_root was created inside runtime_root in a previous run, relocate the existing backup data to the new location before calling reconcile.
  3. Add a startup check that validates !state_root.starts_with(runtime_root) whenever BrowserPaths is built from user config, failing fast with a clear message.

Example fix

// before
let paths = BrowserPaths { runtime_root: cache_dir.clone(), state_root: cache_dir.join("codex-backups") };
reconcile(&paths, enabled)?; // Backups must be outside the cache

// after
let paths = BrowserPaths { runtime_root: cache_dir.clone(), state_root: app_data_dir.join("codex-native-browser-state") };
debug_assert!(!paths.state_root.starts_with(&paths.runtime_root));
reconcile(&paths, enabled)?;
Defensive patterns

Strategy: validation

Validate before calling

fn validate_paths(paths: &BrowserPaths) -> Result<(), String> {
    if paths.state_root.starts_with(&paths.runtime_root) {
        Err("state_root (backups) must not be inside runtime_root".into())
    } else { Ok(()) }
}

Type guard

fn paths_are_safe(paths: &BrowserPaths) -> bool {
    !paths.state_root.starts_with(&paths.runtime_root)
}

Try / catch

match reconcile(&paths, enabled) {
    Err(e) if e.to_string().contains("Backups must be outside the cache") => {
        eprintln!("Fix BrowserPaths: move state_root out of {:?}", paths.runtime_root);
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling reconcile(paths, enabled) (or any of its callers: monitor_once, tests) with a BrowserPaths whose state_root path string starts with runtime_root, e.g. state_root = runtime_root.join("backups").

Common situations: Hand-constructing BrowserPaths for testing or custom setups and nesting the backup folder under the browser's cache/profile directory; a misconfigured environment variable or config pointing the state dir inside the browser profile; migrating paths and accidentally making state_root a subdirectory of runtime_root.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

        );
    }
    Ok(())
}

/// No runtime operation occurs when this feature has never been enabled.
/// Call only from the owning launcher, never from settings save or status inspection.
pub fn reconcile(paths: &BrowserPaths, enabled: bool) -> Result<BrowserStatus> {
    reconcile_contract(paths, enabled, &RuntimeContract::pinned())
}

fn reconcile_contract(
    paths: &BrowserPaths,
    enabled: bool,
    contract: &RuntimeContract,
) -> Result<BrowserStatus> {
    plain_path(&paths.runtime_root)?;
    plain_path(&paths.state_root)?;
    ensure!(
        !paths.state_root.starts_with(&paths.runtime_root),
        "Backups must be outside the cache"
    );
    if !enabled && !paths.state_root.exists() {
        return Ok(BrowserStatus::new("disabled", "Not configured"));
    }
    fs::create_dir_all(&paths.state_root)?;
    let _guards = pin_parents(&paths.state_root.join("owner.lock"))?;
    let lock_path = paths.state_root.join("owner.lock");
    plain_path(&lock_path)?;
    let lock = OpenOptions::new()
        .create(true)
        .truncate(false)
        .write(true)
        .open(lock_path)?;
    lock.try_lock_exclusive()
        .context("Another compatibility transaction is active")?;
    let result = reconcile_locked(paths, enabled, contract);

View on GitHub (pinned to b1ed92e5e4)