BigPizzaV3/CodexPlusPlus · error

Monitor lock is a reparse point

Error message

Monitor lock is a reparse point

What it means

On Windows, after confirming monitor.lock is a regular file, acquire_monitor_owner additionally rejects files whose FILE_ATTRIBUTE_REPARSE_POINT bit (0x400) is set — junctions, symlinks, OneDrive/cloud placeholders, and similar. This is an anti-symlink-attack / anti-placeholder hardening check: a reparse point could redirect the lock to a location controlled by someone else or behave unpredictably under file locking, so the library refuses to use it.

Solutions

  1. Check the attribute: `fsutil reparsepoint query <state_root>\monitor.lock` or `attrib <path>` and look for reparse/L attributes.
  2. Move the state directory to a real local NTFS path (not a junction/symlink/cloud-synced folder) and update the configuration pointing at it.
  3. Dehydrate/convert OneDrive placeholders to local files, or exclude the CodexPlusPlus state dir from cloud sync.
  4. Replace monitor.lock with a plain regular file (delete and let the library recreate it) once the state root is a plain directory.

Example fix

// before: state dir is a junction
mklink /J %APPDATA%\codex-state D:\sync\codex-state
// after: real local directory, not cloud-synced
mkdir %APPDATA%\codex-state  (copy contents, remove the junction)
Defensive patterns

Strategy: validation

Validate before calling

// Windows-only preflight
#[cfg(windows)]
fn has_reparse_point(p: &std::path::Path) -> bool {
    use std::os::windows::fs::MetadataExt;
    std::fs::symlink_metadata(p).map(|m| m.file_attributes() & 0x400 != 0).unwrap_or(false)
}
// refuse to use a state root (or lock path) that is a junction/symlink/cloud placeholder

Type guard

#[cfg(windows)]
fn is_plain_local_file(p: &std::path::Path) -> bool {
    use std::os::windows::fs::MetadataExt;
    std::fs::metadata(p).map(|m| m.is_file() && m.file_attributes() & 0x400 == 0).unwrap_or(false)
}

Try / catch

match start_monitor(&paths, enabled) {
    Err(e) if e.to_string().contains("reparse point") => {
        // relocate state dir to a real local NTFS path and retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling start_monitor_with_contract on Windows when `<state_root>/monitor.lock` carries the reparse-point attribute — e.g. the state directory was replaced with an NTFS junction or symlink, the file is a OneDrive/Files-On-Demand placeholder, or a dev-drive/symlink setup put the lock behind a reparse point.

Common situations: State directory relocated via junction/symlink (common with Dotfiles or moving state off an SSD); OneDrive/Dropbox 'files on demand' placeholder attributes; corporate roaming profiles with folder redirection; WSL/Windows interop creating symlinks.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


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

Appendix: source

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

        "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)
}

fn wait_for_monitor_shutdown_at(paths: &BrowserPaths, timeout: Duration) -> Result<()> {
    let path = paths.state_root.join("monitor.lock");

View on GitHub (pinned to b1ed92e5e4)