JuliusBrussee/caveman · error

${file} already exists with non-canonical content; refusing

Error message

${file} already exists with non-canonical content; refusing to overwrite an unjournaled skill

What it means

In preflightAgentNativeBundleComponents with no existing bundle journal, a skill file that already exists on disk but does not byte-equal the canonical embedded body is treated as foreign user content. Since there is no journal to prove Caveman wrote it, overwriting would destroy someone else's work — hence the refusal.

Source

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

function ensureAgentNativeIntegration(agent: "claude" | "codex"): void {
  const status = nativeIntegrationStatus(agent);
  if (!status.installed) {
    enableNative([agent]);
    return;
  }
  if (status.state !== "installed") repairNativeAgent(agent);
}

function preflightAgentNativeBundleComponents(
  agent: "claude" | "codex",
  existingBundle: AgentNativeBundleJournal | null,
): void {
  const previousSkills = new Map(existingBundle?.skills.map((skill) => [skill.file, skill]) ?? []);
  for (const { file, body } of agentNativeSkillFiles(agent)) {
    const current = fileBytes(file);
    if (!existingBundle) {
      if (current && !current.equals(Buffer.from(body))) {
        throw new Error(`${file} already exists with non-canonical content; refusing to overwrite an unjournaled skill`);
      }
      continue;
    }
    const previous = previousSkills.get(file);
    if (current && !current.equals(Buffer.from(body)) && (!previous || bytesHash(current) !== previous.after_sha256)) {
      throw new Error(`${file} changed after setup; refusing to overwrite user skill edits`);
    }
  }
  if (agent === "claude") {
    const actual = claudeMcpRegistration("caveman-cloud");
    const marker = readMcpServerMarker("claude", "caveman-cloud");
    if (actual.present && !marker) {
      throw new Error("Claude caveman-cloud MCP exists but is not Caveman-journaled; refusing overwrite");
    }
    if (actual.present && marker && !agentNativeCloudMcpMatches("claude", marker)) {
      throw new Error("Claude caveman-cloud MCP changed after setup; refusing to overwrite user fields");
    }
    return;

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. If the existing file is yours, back it up and move it away (or rename its directory), then re-run setup
  2. If it is a stale Caveman artifact from an unjournaled older install, verify and delete it so setup can write the canonical body
  3. If you want to keep a customized version, maintain it under a different skill name so it cannot collide
Defensive patterns

Strategy: validation

Validate before calling

function skillPathIsFreeOrCanonical(file: string, canonicalBody: Buffer): boolean {
  try {
    const cur = readFileSync(file);
    return cur.equals(canonicalBody);
  } catch { return true; } // absent = fine
}

Type guard

function isUnjournaledSkillError(e: unknown): boolean {
  return e instanceof Error && e.message.includes("already exists with non-canonical content");
}

Try / catch

try {
  runCavemanSetup();
} catch (e) {
  if (isUnjournaledSkillError(e)) {
    // message names the file — back it up, move it away, re-run
  } else throw e;
}

Prevention

When it happens

Trigger: Running agent-native setup when ~/.claude/skills/<name>/SKILL.md (or the codex equivalent) already exists from an older Caveman version with a different body, or was created by the user/another tool with the same name.

Common situations: Name collision between Caveman's agent-native suite and a user skill; leftover files from a manual install or a previous CLI version whose embedded bodies changed; a different product's skill with the same directory name.

Related errors


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