JuliusBrussee/caveman · error

already exists and is not Caveman-owned; refusing to…

Error message

${extensionPath} already exists and is not Caveman-owned; refusing to overwrite it

What it means

During Caveman's native Pi install, the installer writes a persistent extension file at ~/.pi/agent/extensions/caveman-native.js. If that file already exists but lacks the 'caveman:native-pi' marker, the installer refuses to overwrite it. This protects user-authored Pi extensions from being clobbered by Caveman's generated file.

Solutions

  1. Back up and remove (or rename) ~/.pi/agent/extensions/caveman-native.js, then re-run the Pi enable command.
  2. If the file is your own extension, move it to a different filename in the extensions directory.
  3. If it was a Caveman file whose marker was lost, run `caveman doctor pi` to detect and repair the drift.
  4. Re-run the enable flow to regenerate the extension.

Example fix

// before
~/.pi/agent/extensions/caveman-native.js  // no 'caveman:native-pi' marker
// after
mv ~/.pi/agent/extensions/caveman-native.js ~/.pi/agent/extensions/caveman-native.js.bak
// re-run: caveman enable  -> regenerates file with '// caveman:native-pi'
Defensive patterns

Strategy: fallback

Validate before calling

import { readFileSync, existsSync } from 'fs';
const p = join(homedir(), '.pi', 'agent', 'extensions', 'caveman-native.js');
if (existsSync(p) && !readFileSync(p, 'utf8').includes('caveman:native-pi')) {
  // rename or remove before enabling
}

Try / catch

try {
  await cavemanEnablePi();
} catch (e) {
  if (String(e.message).includes('already exists and is not Caveman-owned')) {
    rename(extensionPath, extensionPath + '.bak');
    await cavemanEnablePi();
  }
}

Prevention

When it happens

Trigger: Running the Pi enable flow when ~/.pi/agent/extensions/caveman-native.js exists and its contents do not include the 'caveman:native-pi' marker string.

Common situations: A user already placed their own caveman-native.js extension; a file from another tool occupies the path; an older Caveman install wrote the file without the current marker; the marker comment was stripped by a minifier or edit.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/e3bf476ad383c014. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/index.ts:7380

      owned: { routes, previous_routes: previousRoutes, installed_mcp: installedMcp, previous_mcp: previousMcp ?? null },
    },
    {
      file: pluginPath,
      before: pluginBefore,
      after: Buffer.from(opencodeNativePluginSource()),
      kind: "opencode-plugin",
    },
  ];
}

function piNativeMutations(): NativeMutation[] {
  const extensionDir = join(homedir(), ".pi", "agent", "extensions");
  // Pi's extension auto-discovery accepts only .ts/.js (isExtensionFile in
  // pi-coding-agent 0.84.2); a .mjs here would journal fine and never load.
  const extensionPath = join(extensionDir, "caveman-native.js");
  const before = fileBytes(extensionPath);
  if (before && !before.toString("utf8").includes("caveman:native-pi")) {
    throw new Error(`${extensionPath} already exists and is not Caveman-owned; refusing to overwrite it`);
  }
  // Plain `pi` launches carry no wrap env, so the persistent artifact bakes in
  // the invocation this enable resolved (falling back to PATH when unset) —
  // same pattern as the generated opencode plugin.
  const { cmd, pre } = cavemanInvocation();
  const after = Buffer.concat([
    Buffer.from("// caveman:native-pi — GENERATED by `caveman enable pi`.\n"),
    Buffer.from(`process.env.CAVEMAN_PI_HOOK_CMD ??= ${JSON.stringify(JSON.stringify([cmd, ...pre]))};\n`),
    readFileSync(resolvePiExtension()),
  ]);
  return [{
    file: extensionPath,
    before,
    after,
    kind: "pi-extension",
    owned: { sha256: bytesHash(after), created_extension_dir: !existsSync(extensionDir) },
  }];
}

View on GitHub (pinned to 3ee70a1026)