BigPizzaV3/CodexPlusPlus · error

Invalid native cleanup receipt

Error message

Invalid native cleanup receipt

What it means

wait_for_monitor_shutdown_at polls monitor.lock until the previous monitor process releases its exclusive lock (meaning cleanup finished). Once the lock is acquired, it validates the cleanup receipt written there: the file must be at most 1024 bytes. This error means the lock was free but the receipt file is larger than the allowed 1024 bytes, so it cannot be a valid MonitorReceipt and the library refuses to trust it.

Solutions

  1. Confirm the size: `ls -l <state_root>/monitor.lock` (or `dir` on Windows); anything over 1024 bytes is invalid.
  2. Delete the corrupt monitor.lock and let the next monitor run recreate it with a fresh receipt.
  3. Check the diagnostic log (native_browser.compatibility entries) to find which generation/process wrote the oversized file.
  4. Ensure all CodexPlusPlus processes/builds on the machine use the same version so receipt format matches.

Example fix

// before: oversized receipt
$ ls -l monitor.lock ; -rw-r--r-- 4096 monitor.lock
// after
rm monitor.lock && rerun monitor start  # fresh 1024-byte-capped receipt
Defensive patterns

Strategy: try-catch

Validate before calling

let lock = state_root.join("monitor.lock");
if let Ok(meta) = std::fs::metadata(&lock) {
    if meta.len() > 1024 {
        // receipt is oversized/corrupt: delete monitor.lock and rerun cleanup before waiting
    }
}

Type guard

fn receipt_size_plausible(p: &std::path::Path) -> bool {
    std::fs::metadata(p).map(|m| m.len() <= 1024).unwrap_or(false)
}

Try / catch

match wait_for_monitor_shutdown(Duration::from_secs(30)) {
    Err(e) if e.to_string().contains("Invalid native cleanup receipt") => {
        // treat receipt as corrupt: remove monitor.lock, restore service files from backups, retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling wait_for_monitor_shutdown (Windows) after monitor cleanup when monitor.lock contains more than 1024 bytes — e.g. a corrupted/oversized receipt, garbage appended by another process, repeated writes without truncation, or the file was overwritten by an unrelated tool.

Common situations: A crashed monitor writing a partial/oversized receipt; disk corruption; another process appending to monitor.lock; a user or script editing the lock file; version mismatch where an older/newer build wrote a different format.

Understand the failure class

Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.

Related errors


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

Appendix: source

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

    options.read(true).write(true);
    #[cfg(windows)]
    {
        use std::os::windows::fs::OpenOptionsExt;
        options.share_mode(0x1 | 0x2).custom_flags(0x00200000);
    }
    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()),

View on GitHub (pinned to b1ed92e5e4)