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

  1. Replace the symlink at the reported path with a real directory or file so the mutation target is concrete
  2. Point the relevant config/env variable directly at the real location instead of through a symlink
  3. 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.