Hmbown/CodeWhale · error
provider catalog lock
Error message
provider catalog lock {} must not be hard linked What it means
On Unix, the provider catalog lock file must have an nlink count of 1. If it is hard linked elsewhere, another process could swap the underlying inode and bypass the advisory lock, so locking is refused as a security/integrity guard.
Solutions
- Delete the lock file (`rm <path>`) and retry; it is recreated with a single link.
- Find the other links with `find / -samefile <path> 2>/dev/null` and remove them or exclude the cache dir from dedup tools.
- Reconfigure backup/dedup tooling to skip the codewhale cache directory.
- Restore the cache directory from a clean state if links keep reappearing.
Example fix
// before: lock has 2 links $ stat -c %h ~/.cache/codewhale/provider-catalog.lock 2 // after $ rm ~/.cache/codewhale/provider-catalog.lock $ stat -c %h ~/.cache/codewhale/provider-catalog.lock # recreated as 1
Defensive patterns
Strategy: validation
Validate before calling
import { statSync } from 'node:fs';
if (process.platform !== 'win32') {
const st = statSync(lockPath);
if (st.nlink > 1) {
console.error(`${lockPath} is hard linked ${st.nlink}x — remove before running`);
process.exit(1);
}
} Type guard
const hasSingleLink = (p) => { try { return statSync(p).nlink === 1; } catch { return false; } }; Try / catch
try {
await codewhale.providers.refresh();
} catch (e) {
if (e.message.includes('must not be hard linked')) {
fs.rmSync(lockPath, { force: true });
return codewhale.providers.refresh();
}
throw e;
} Prevention
- Exclude the codewhale cache directory from dedup/hard-link tools (rsync --link-dest, git-annex).
- Never hard-link files out of the cache directory manually.
- If links reappear after backups, restore the cache from a fresh run instead.
When it happens
Trigger: `open_cache_lock` (called by `load_from_disk`, `persist_scope`, `persist_failure_scope`) on Unix sees `metadata.nlink() != 1` — the lock file has additional hard links.
Common situations: Backup/dedup tools (rsync --link-dest, git-annex, content-addressed stores) hard-linked the cache file, a user hard-linked the lock across directories, or a snapshot/restore tool relinked cache files.
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
- private sidecar file
- Automation lock must not have hard links
- Codewhale credentials directory must be owned by the…
- Codewhale-owned credential file must be singly linked…
- continue_goal with a wire-supplied schedule id still parses
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/f9657533a24e6729.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/provider_catalog_live.rs:545
{
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!(
metadata.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT == 0,
"provider catalog lock {} must not be a reparse point",
path.display()
);
}
Ok(file)
}
View on GitHub (pinned to 73e0f67d83)