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
- Inspect `<state_root>/monitor.lock` (`ls -la` / `file` on it) to see what it actually is.
- Delete the bogus object (after confirming it is not a real file you need) and let the library recreate it on next start.
- Verify the state_root points at the intended CodexPlusPlus state directory, not a directory shared with other tooling or cloud sync.
- 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
- Keep the state root on a plain local filesystem, not one managed by sync clients or shared with other tools
- Never create files named monitor.lock yourself; let the library own that path
- Before starting the monitor, assert the state root contains only expected regular files
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
- 拒绝删除 CODEX_HOME 的祖先目录
- Invalid native cleanup receipt
- Codex CLI 路径指向的是目录而不是可执行文件:
- Codex CLI 没有执行权限:
- Codex CLI 路径不存在: 请填 Codex CLI 可执行文件的完整路径。macOS 桌面版通常在…
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)