Hmbown/CodeWhale · error

provider catalog lock

Error message

provider catalog lock {} must be a regular file

What it means

The provider catalog's on-disk cache uses a lock file with advisory locking. Before locking, `open_cache_lock` verifies the path is a regular file; if it is a directory, FIFO, socket, or device node, locking semantics are unsafe and the operation fails loudly rather than silently misbehaving.

Solutions

  1. Inspect the path shown in the message with `ls -la` and remove/replace the non-regular entry (e.g. `rm -rf <lock-path>` if it is a directory).
  2. Restart the operation; the lock file is recreated as a regular file.
  3. Check that no container volume or sync tool is mounted at that path.
  4. If it recurs, clear the whole provider-catalog cache directory.

Example fix

// before: lock path is a directory
$ ls -la ~/.cache/codewhale/provider-catalog.lock
drwxr-xr-x provider-catalog.lock
// after
$ rm -rf ~/.cache/codewhale/provider-catalog.lock
$ codewhale   # lock recreated as a regular file
Defensive patterns

Strategy: validation

Validate before calling

import stat from 'node:fs';
const st = stat.statSync(lockPath);
if (!st.isFile()) {
  console.error(`${lockPath} is not a regular file — remove it before running`);
  process.exit(1);
}

Type guard

const isRegularFile = (p) => { try { return require('fs').statSync(p).isFile(); } catch { return false; } };

Try / catch

try {
  await codewhale.providers.refresh();
} catch (e) {
  if (e.message.includes('must be a regular file')) {
    fs.rmSync(lockPath, { recursive: true, force: true });
    return codewhale.providers.refresh();
  }
  throw e;
}

Prevention

When it happens

Trigger: `open_cache_lock` (called by `load_from_disk`, `persist_scope`, `persist_failure_scope`) opens the lock path and `metadata.is_file()` is false — e.g. something created a directory at the lock file's path.

Common situations: A previous crash or bad tooling created a directory named like the lock file, a container/tmpfs volume mounted at that path, dotfile-sync tools replacing the lock with a symlink/dir, or permission-restricted paths resolving oddly.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/b211ef9c31183ef1. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/provider_catalog_live.rs:537

    #[cfg(unix)]
    {
        use std::os::unix::fs::OpenOptionsExt as _;
        options
            .mode(0o600)
            .custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC | libc::O_NONBLOCK);
    }
    #[cfg(windows)]
    {
        use std::os::windows::fs::OpenOptionsExt as _;
        options.custom_flags(0x0020_0000); // FILE_FLAG_OPEN_REPARSE_POINT
    }
    let file = options
        .open(path)
        .with_context(|| format!("open provider catalog lock {}", path.display()))?;
    let metadata = file
        .metadata()
        .with_context(|| format!("inspect provider catalog lock {}", path.display()))?;
    anyhow::ensure!(
        metadata.is_file(),
        "provider catalog lock {} must be a regular file",
        path.display()
    );
    #[cfg(unix)]
    {
        use std::os::unix::fs::MetadataExt as _;
        anyhow::ensure!(
            metadata.nlink() == 1,
            "provider catalog lock {} must not be hard linked",
            path.display()
        );
    }
    #[cfg(windows)]
    {
        use std::os::windows::fs::MetadataExt as _;
        const FILE_ATTRIBUTE_REPARSE_POINT: u32 = 0x0000_0400;
        anyhow::ensure!(

View on GitHub (pinned to 73e0f67d83)