JuliusBrussee/caveman · error

${pluginPath} already exists and is not Caveman-owned; refus

Error message

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

What it means

opencodeNativeMutations() writes a Caveman-owned plugin file (opencodeNativePluginPath(), an opencode plugin JS file) and only overwrites it when the existing content contains the "caveman:native-opencode" ownership marker. Any pre-existing file without that marker is treated as user-owned and protected from overwrite.

Source

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

  for (const [providerID, route] of Object.entries(routes)) {
    const provider = providers[providerID] && typeof providers[providerID] === "object" && !Array.isArray(providers[providerID]) ? providers[providerID] as Record<string, unknown> : {};
    const options = provider.options && typeof provider.options === "object" && !Array.isArray(provider.options) ? provider.options as Record<string, unknown> : {};
    previousRoutes[providerID] = options.baseURL ?? null;
    options.baseURL = route;
    provider.options = options;
    providers[providerID] = provider;
  }
  root.provider = providers;
  const mcp = root.mcp && typeof root.mcp === "object" && !Array.isArray(root.mcp) ? root.mcp as Record<string, unknown> : {};
  const previousMcp = mcp.caveman;
  const installedMcp = { type: "local", command: [mcpBinary], enabled: true };
  mcp.caveman = installedMcp;
  root.mcp = mcp;

  const pluginPath = opencodeNativePluginPath();
  const pluginBefore = fileBytes(pluginPath);
  if (pluginBefore && !pluginBefore.toString("utf8").includes("caveman:native-opencode")) {
    throw new Error(`${pluginPath} already exists and is not Caveman-owned; refusing to overwrite it`);
  }
  return [
    {
      file: configPath,
      before,
      after: Buffer.from(JSON.stringify(root, null, 2) + "\n"),
      kind: "opencode-config",
      owned: { routes, previous_routes: previousRoutes, installed_mcp: installedMcp, previous_mcp: previousMcp ?? null },
    },
    {
      file: pluginPath,
      before: pluginBefore,
      after: Buffer.from(opencodeNativePluginSource()),
      kind: "opencode-plugin",
    },
  ];
}

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Open the plugin file, confirm it is not something you need, and move it aside (e.g. plugin.js.bak) or delete it
  2. If it came from an old caveman install, delete it so the current version rewrites it with the current marker
  3. Re-run the opencode native install; it will now create the file with proper ownership marker

Example fix

# before
$ ls ~/.config/opencode/plugin/caveman.js   # exists, user-authored
Error: .../caveman.js already exists and is not Caveman-owned; refusing to overwrite it

# after
$ mv ~/.config/opencode/plugin/caveman.js ~/caveman.js.bak
$ caveman native install opencode   # recreates Caveman-owned plugin
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync, readFileSync } from "node:fs";
function pluginFileSafeToOwn(path: string): boolean {
  if (!existsSync(path)) return true;
  try { return readFileSync(path, "utf8").includes("caveman:native-opencode"); }
  catch { return false; }
}

Try / catch

try { nativeInstallOpencode(); } catch (e) {
  if (e instanceof Error && /not Caveman-owned/.test(e.message)) {
    backUpAndRemovePlugin(); nativeInstallOpencode();
  } else throw e;
}

Prevention

When it happens

Trigger: Native opencode install when the plugin file path already exists and its contents lack the caveman:native-opencode marker string.

Common situations: User (or another tool) already placed their own plugin at that path; a leftover plugin from an incompatible/older caveman version whose marker text changed; manually created stub file.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/cec828983101d7a4. Report an issue: GitHub.