BigPizzaV3/CodexPlusPlus · error

Unexpected monitor lock type

Error message

Unexpected monitor lock type

What it means

acquire_monitor_owner opens (or creates) the file `<state_root>/monitor.lock`, which is used as an exclusive advisory lock to ensure only one native browser monitor runs. Before locking, it validates that the path is a regular file. This ensure! fires when the filesystem object at that path is not a regular file (e.g. a directory, FIFO, device node, or symlink target resolving to something else), so the library refuses to use it as a lock and aborts startup.

Solutions

  1. Inspect `<state_root>/monitor.lock` (`ls -la` / `file` on it) to see what it actually is.
  2. Delete the bogus object (after confirming it is not a real file you need) and let the library recreate it on next start.
  3. Verify the state_root points at the intended CodexPlusPlus state directory, not a directory shared with other tooling or cloud sync.
  4. Re-run the monitor start; if it recurs, check for software on the machine that creates symlinks/pipes at that path (security software, tampering).

Example fix

// before (manual state dir)
mkdir ~/.codexplusplus-state/monitor.lock
// after
rm -rf ~/.codexplusplus-state/monitor.lock  # replace non-file object with nothing; library recreates it
Defensive patterns

Strategy: validation

Validate before calling

let lock = state_root.join("monitor.lock");
if lock.symlink_metadata().is_ok() && !lock.symlink_metadata().unwrap().is_file() {
    // non-file object at lock path: remove it or refuse to start the monitor
    std::fs::remove_file(&lock)?; // only after confirming it's safe to delete
}

Type guard

fn is_regular_file(p: &std::path::Path) -> bool {
    std::fs::symlink_metadata(p).map(|m| m.is_file()).unwrap_or(false)
}

Try / catch

match native_browser::start_monitor(paths, enabled) {
    Err(e) if e.to_string().contains("Unexpected monitor lock type") => {
        // clear the bogus lock object and retry once
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling start_monitor_with_contract (via the manager's monitor start flow) when `<state_root>/monitor.lock` exists but is not a regular file — e.g. someone created a directory named monitor.lock, the path was replaced by a named pipe/socket, or a symlink/device node was placed there.

Common situations: A leftover or malicious file in the CodexPlusPlus state directory; backup/restore tools or sync clients (Dropbox, OneDrive) replacing the lock with a non-file object; a user or script manually creating `monitor.lock` as a directory; container/overlay filesystems materializing the path oddly.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

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

fn acquire_monitor_owner(paths: &BrowserPaths) -> Result<File> {
    plain_path(&paths.state_root)?;
    ensure!(
        !paths.state_root.starts_with(&paths.runtime_root),
        "Backups must be outside the cache"
    );
    fs::create_dir_all(&paths.state_root)?;
    let path = paths.state_root.join("monitor.lock");
    let _guards = pin_parents(&path)?;
    let mut options = OpenOptions::new();
    options.read(true).write(true).create(true).truncate(false);
    #[cfg(windows)]
    {
        use std::os::windows::fs::OpenOptionsExt;
        options.share_mode(0x1 | 0x2).custom_flags(0x00200000);
    }
    let owner = options.open(&path)?;
    let meta = owner.metadata()?;
    ensure!(meta.is_file(), "Unexpected monitor lock type");
    #[cfg(windows)]
    {
        use std::os::windows::fs::MetadataExt;
        ensure!(meta.file_attributes() & 0x400 == 0, "Monitor lock is a reparse point");
    }
    plain_path(&path)?;
    owner.try_lock_exclusive().context("Another native browser monitor is active")?;
    Ok(owner)
}

/// Called after Codex has been stopped, before the manager launches a replacement.
/// Never restores files itself or creates a lock for an older launcher.
pub fn wait_for_monitor_shutdown(timeout: Duration) -> Result<()> {
    if !cfg!(windows) {
        return Ok(());
    }
    let paths = BrowserPaths::current()?;
    wait_for_monitor_shutdown_at(&paths, timeout)

View on GitHub (pinned to b1ed92e5e4)