BigPizzaV3/CodexPlusPlus · error

Native browser cleanup did not complete successfully

Error message

Native browser cleanup did not complete successfully

What it means

After acquiring the released monitor.lock, the library deserializes the MonitorReceipt JSON and requires schema == 1, a parseable UUID in `generation`, and state == "restored". This error means the lock was free (the previous monitor finished) but its receipt does not record a successful cleanup — either the fields are malformed or the recorded state is not `restored` (e.g. "active" or an error state), so the shutdown cannot be considered complete.

Solutions

  1. Read monitor.lock and inspect the JSON: check `schema`, `generation`, and `state` fields.
  2. If state is not "restored", run the cleanup/restore path (or verify_restored_state's checks manually: control.json requireIdentification=false, service files match originals) before launching a replacement.
  3. If the JSON is corrupt, delete monitor.lock and restore browser service files from backups in the state directory.
  4. Check the diagnostic log for the monitor's last recorded action to determine how far cleanup got.

Example fix

// before: stale receipt
{"schema":1,"generation":"...","state":"active"}
// after: rerun cleanup so it writes
{"schema":1,"generation":"<uuid>","state":"restored"}
Defensive patterns

Strategy: try-catch

Validate before calling

if let Ok(bytes) = std::fs::read(state_root.join("monitor.lock")) {
    if let Ok(v) = serde_json::from_slice::<serde_json::Value>(&bytes) {
        let ok = v["schema"] == 1 && v["state"] == "restored";
        if !ok { /* stale/failed receipt: run cleanup before launching replacement */ }
    }
}

Type guard

fn receipt_restored(bytes: &[u8]) -> bool {
    serde_json::from_slice::<serde_json::Value>(bytes)
        .map(|v| v["schema"] == 1 && v["state"] == "restored" && v["generation"].is_string())
        .unwrap_or(false)
}

Try / catch

match wait_for_monitor_shutdown(Duration::from_secs(30)) {
    Err(e) if e.to_string().contains("cleanup did not complete successfully") => {
        // previous monitor died mid-cleanup: run restore/verify before launching the new launcher
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling wait_for_monitor_shutdown when the receipt was left in a non-restored state (monitor died mid-cleanup), or the receipt is corrupt/invalid JSON so schema/generation checks fail; also stale receipts from an interrupted earlier generation.

Common situations: The native browser monitor process was killed (taskkill, crash, reboot) between writing "active" and "restored"; power loss during cleanup; hand-edited or corrupted receipt; version skew writing different schema numbers.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

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

    }
    let mut file = match options.open(&path) {
        Ok(file) => file,
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
            return verify_restored_state(paths);
        }
        Err(error) => return Err(error.into()),
    };
    plain_path(&path)?;
    let deadline = std::time::Instant::now() + timeout;
    loop {
        match file.try_lock_exclusive() {
            Ok(()) => {
                ensure!(file.metadata()?.len() <= 1024, "Invalid native cleanup receipt");
                file.seek(SeekFrom::Start(0))?;
                let mut bytes = Vec::new();
                Read::by_ref(&mut file).take(1025).read_to_end(&mut bytes)?;
                let receipt: MonitorReceipt = serde_json::from_slice(&bytes)?;
                ensure!(
                    receipt.schema == 1 && uuid::Uuid::parse_str(&receipt.generation).is_ok()
                        && receipt.state == "restored",
                    "Native browser cleanup did not complete successfully"
                );
                return Ok(());
            }
            Err(error) if error.kind() == fs2::lock_contended_error().kind() => {
                ensure!(
                    std::time::Instant::now() < deadline,
                    "Native browser cleanup is still running; launcher was not terminated"
                );
                std::thread::sleep(Duration::from_millis(50).min(
                    deadline.saturating_duration_since(std::time::Instant::now()),
                ));
            }
            Err(error) => return Err(error.into()),
        }
    }

View on GitHub (pinned to b1ed92e5e4)