{"record":{"id":"53356c1ac8f701b7","repo":"JuliusBrussee/caveman","slug":"path-is-a-symlink-refusing-transactional-owner","errorCode":null,"errorMessage":"${path} is a symlink; refusing transactional ownership mutation","messagePattern":"(.+?) is a symlink; refusing transactional ownership mutation","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":10269,"sourceCode":"// bare name so callers' existing missing-binary handling still triggers.\nfunction cavemanBin(name: string, envVar: string): string {\n  const explicit = process.env[envVar];\n  if (explicit) return explicit;\n  const onPath = which(name);\n  if (onPath) return onPath;\n  const local = join(cavemanHome(), \"bin\", binaryInstallFilename(name));\n  if (isExecutable(local)) return local;\n  return name;\n}\nfunction mcpServerMarkerPath(agentId: string, serverName: string): string {\n  return join(cavemanHome(), \"mcp\", serverName === \"caveman\" ? `${agentId}.json` : `${agentId}.${serverName}.json`);\n}\n\nfunction canonicalOwnedMcpMarkerPath(agent: \"kilo\" | \"qwen\", serverName: string): string {\n  const path = mcpServerMarkerPath(agent, serverName);\n  try {\n    if (lstatSync(path).isSymbolicLink()) {\n      throw new Error(`${path} is a symlink; refusing transactional ownership mutation`);\n    }\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code !== \"ENOENT\") throw error;\n  }\n  return canonicalMcpConfigPath(path);\n}\n\n// A native MCP registration and its Caveman ownership journal form one logical\n// write. Prove the journal directory is writable before touching agent config;\n// otherwise Kilo/Qwen would refuse both a later upgrade and removal because the\n// surviving registration has no trustworthy owner.\nfunction preflightMcpServerMarker(agentId: string, serverName: string): void {\n  const path = mcpServerMarkerPath(agentId, serverName);\n  const probe = join(dirname(path), `.${basename(path)}.preflight-${process.pid}-${randomUUID()}`);\n  try {\n    mkdirSync(dirname(path), { recursive: true, mode: 0o700 });\n    chmodSync(dirname(path), 0o700);\n    durableAtomicWriteFile(probe, Buffer.alloc(0));","sourceCodeStart":10251,"sourceCodeEnd":10287,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/5184b3d11ac6a1acb7d44b9bfaa31698157cff97/packages/cli/src/index.ts#L10251-L10287","documentation":"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.","triggerScenarios":"Thrown at packages/cli/src/index.ts:10269 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"5184b3d11ac6a1acb7d44b9bfaa31698157cff97","analyzedAt":"2026-09-06T12:00:26.372Z","contentChangedAt":"2026-09-06T12:00:26.372Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}