Hmbown/CodeWhale · error · anyhow::Error

config lock was redirected while opening {}

Error message

config lock was redirected while opening {}

What it means

Windows-only companion to the reparse-point check: the opened lock file handle is resolved to its final NTFS path via GetFinalPathNameByHandleW and compared (normalized) against the expected path. A mismatch means the open was redirected — the file you locked is not the file at the expected path, so locking would not actually serialize writers.

Source

Thrown at crates/config/src/config_document.rs:278

    let mut buffer = vec![0u16; needed as usize + 1];
    // SAFETY: `buffer` is writable for its declared length and `handle` stays
    // valid through the call.
    let written = unsafe {
        GetFinalPathNameByHandleW(handle, buffer.as_mut_ptr(), buffer.len() as u32, flags)
    };
    if written == 0 || written as usize >= buffer.len() {
        return Err(std::io::Error::last_os_error()).with_context(|| {
            format!(
                "failed to resolve config lock at {}",
                crate::quote_os_path(expected_path)
            )
        });
    }
    let actual = OsString::from_wide(&buffer[..written as usize]);
    if normalize_windows_path_for_comparison(Path::new(&actual))?
        != normalize_windows_path_for_comparison(expected_path)?
    {
        bail!(
            "config lock was redirected while opening {}",
            crate::quote_os_path(expected_path)
        );
    }
    Ok(())
}

#[cfg(windows)]
fn normalize_windows_path_for_comparison(path: &Path) -> Result<String> {
    let text = path.to_str().ok_or_else(|| {
        anyhow::anyhow!(
            "config lock path {} contains invalid Unicode and cannot be compared safely",
            crate::quote_os_path(path)
        )
    })?;
    let without_device_prefix = text.strip_prefix(r"\\?\").unwrap_or(text);
    let normalized_prefix = without_device_prefix.strip_prefix("UNC\\").map_or_else(
        || without_device_prefix.to_string(),

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Always invoke Codewhale with the same form of config path (same drive letter, no mixed subst/mapped aliases) across processes
  2. Remove subst/mapped-drive or junction indirection from the config directory path
  3. Delete stale lock files and let the next write recreate them under the consistent path

Example fix

# before
subst X: C:\Users\me   # one session uses X:\...\.config, another C:\Users\me\...\.config

# after
subst X: /d
# use the canonical C:\Users\me\... path in all sessions
Defensive patterns

Strategy: validation

Validate before calling

// Ensure all processes use one canonical config path form:
fn canonical_config_dir() -> std::path::PathBuf {
    // resolve subst/mapped drives once at startup and reuse everywhere
    dunce::canonicalize(std::path::Path::new(&env::var("CODEWHALE_CONFIG").unwrap_or_default()))
        .unwrap_or_else(|_| default_config_dir())
}

Try / catch

match with_config_write_lock(&path, op) {
    Ok(v) => v,
    Err(e) if e.to_string().contains("lock was redirected") => {
        // path alias mismatch: unify path form across processes and retry
        user_action_then_retry(e)
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Opening the expected lock path actually yielded a different final path: 8.3 short-name mismatch handled differently, subst/ mapped-drive prefixes, a junction inside the path chain, or case/normalization differences that survive the comparison function.

Common situations: Config accessed through a subst drive or mapped network path in one process and the real path in another; drive-letter vs `\\?\` volume-name prefixes; junction-based profile redirection changing the final path.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/14650d665531e936. Report an issue: GitHub.