JuliusBrussee/caveman · error · Error
${path} is a symlink; refusing transactional ownership mutat
Error message
${path} is a symlink; refusing transactional ownership mutation What it means
Refusal guard during transactional ownership mutation of an MCP-related path: the target path (or an ancestor) is a symbolic link. Because the transaction writes and rolls back files by path, a symlink could redirect writes outside the intended location, so mutation is refused up front.
Source
Thrown at packages/cli/src/index.ts:10269
// bare name so callers' existing missing-binary handling still triggers.
function cavemanBin(name: string, envVar: string): string {
const explicit = process.env[envVar];
if (explicit) return explicit;
const onPath = which(name);
if (onPath) return onPath;
const local = join(cavemanHome(), "bin", binaryInstallFilename(name));
if (isExecutable(local)) return local;
return name;
}
function mcpServerMarkerPath(agentId: string, serverName: string): string {
return join(cavemanHome(), "mcp", serverName === "caveman" ? `${agentId}.json` : `${agentId}.${serverName}.json`);
}
function canonicalOwnedMcpMarkerPath(agent: "kilo" | "qwen", serverName: string): string {
const path = mcpServerMarkerPath(agent, serverName);
try {
if (lstatSync(path).isSymbolicLink()) {
throw new Error(`${path} is a symlink; refusing transactional ownership mutation`);
}
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
}
return canonicalMcpConfigPath(path);
}
// A native MCP registration and its Caveman ownership journal form one logical
// write. Prove the journal directory is writable before touching agent config;
// otherwise Kilo/Qwen would refuse both a later upgrade and removal because the
// surviving registration has no trustworthy owner.
function preflightMcpServerMarker(agentId: string, serverName: string): void {
const path = mcpServerMarkerPath(agentId, serverName);
const probe = join(dirname(path), `.${basename(path)}.preflight-${process.pid}-${randomUUID()}`);
try {
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
chmodSync(dirname(path), 0o700);
durableAtomicWriteFile(probe, Buffer.alloc(0));View on GitHub (pinned to 5184b3d11a)
Solutions
- Replace the symlink at the reported path with a real directory or file so the mutation target is concrete
- Point the relevant config/env variable directly at the real location instead of through a symlink
- Resolve the symlink target yourself and re-run the operation against the resolved path
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at packages/cli/src/index.ts:10269 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-09-06).
Data as JSON: /api/errors/53356c1ac8f701b7.
Report an issue: GitHub.