JuliusBrussee/caveman · error · Error

already exists with non-canonical content; refusing to…

Error message

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

What it means

When installing agent-native skills with no existing journaled bundle, the CLI refuses to overwrite a skill file that already exists on disk with content differing from the canonical body. Without a journal proving Caveman created the file, overwriting could destroy a user's own file, so it throws instead.

Solutions

  1. Move or delete the pre-existing skill file so the canonical version can be installed
  2. Replace the file's content with the canonical body so the check passes
  3. Install the bundle manually and let the journal take ownership of the files

Example fix

// before
throw new Error(`${file} already exists with non-canonical content; refusing to overwrite an unjournaled skill`);
// after
// preserve the user's file, then install:
renameSync(file, `${file}.user-backup`);
writeFileSync(file, Buffer.from(body));
Defensive patterns

Strategy: validation

Validate before calling

const target = join(skillDir, 'my-skill.md');
if (existsSync(target)) {
  const canonical = await fetchCanonicalBody();
  if (!readFileSync(target).equals(Buffer.from(canonical))) {
    console.error(`${target} exists with custom content; move it before setup.`);
  }
}

Try / catch

try {
  runSetup(agent);
} catch (e) {
  if (String(e.message).includes('non-canonical content')) {
    // rename the existing file to *.user-backup and re-run setup
  }
}

Prevention

When it happens

Trigger: Running setup when no existing bundle journal is present and a target skill file already exists with non-canonical content (current bytes !== canonical body).

Common situations: User created a skill file at the same path themselves; a previous unjournaled/experimental install left files behind; upgrading from a version that did not journal skills.

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/a3ddf29de09ee5ed. Report an issue: GitHub.

Appendix: source

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

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 3ee70a1026)