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
- Read monitor.lock and inspect the JSON: check `schema`, `generation`, and `state` fields.
- 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.
- If the JSON is corrupt, delete monitor.lock and restore browser service files from backups in the state directory.
- 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
- Always stop the monitor via the library's graceful shutdown so it writes state="restored"
- Avoid killing monitor processes (taskkill / power loss) mid-cleanup; use UPS/graceful shutdown on machines running the manager
- Run verify_restored_state-equivalent checks (control.json, service files) before launching a replacement launcher
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)