JuliusBrussee/caveman · warning · Error

caveman-code: old_string and new_string are identical

Error message

caveman-code: old_string and new_string are identical

What it means

edit_file refuses a no-op edit: old_string and new_string must differ byte-for-byte. This is an upfront guard so callers notice malformed edit requests (e.g. a template that substitutes the same value into both fields) before any disk write is attempted.

Source

Thrown at packages/agent/src/code.ts:343

  });

  const editTool = tool({
    name: "edit_file",
    description:
      "Replace an exact string in a workspace file. The old string must appear exactly " +
      "once unless replace_all is set. Writes to disk.",
    input: schema.object({
      path: schema.string(),
      old_string: schema.string(),
      new_string: schema.string(),
      replace_all: schema.optional(schema.boolean()),
    }),
    effect: "write",
    result: "inline",
    timeoutMs: READ_TIMEOUT_MS,
    async execute(input) {
      if (input.old_string === input.new_string) {
        throw new Error("caveman-code: old_string and new_string are identical");
      }
      const target = await contained(input.path);
      const content = await readFile(target, "utf8");
      const occurrences = content.split(input.old_string).length - 1;
      if (occurrences === 0) {
        throw new Error(`caveman-code: old_string not found in ${input.path}`);
      }
      if (occurrences > 1 && input.replace_all !== true) {
        throw new Error(
          `caveman-code: old_string appears ${occurrences} times in ${input.path}; ` +
          "add surrounding context or pass replace_all",
        );
      }
      // split/join UNCONDITIONALLY. String.prototype.replace
      // interprets `$&`, `$\``, `$'`, `$$`, `$1`… in the REPLACEMENT even for a
      // string pattern, so a new_string containing any of them would silently
      // corrupt the file. The non-replace_all branch is guaranteed exactly one
      // occurrence above, so joining replaces precisely that one.

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Diff your old/new strings before calling edit_file; if they are equal, the edit is unnecessary — skip it
  2. Fix the upstream templating/substitution bug that made the two fields collapse to the same value
  3. If you intended a no-op touch (e.g. mtime), use a different mechanism — edit_file will never allow it

Example fix

// before
await edit_file({ path, old_string: snippet, new_string: snippet });

// after
if (oldString !== newString) {
  await edit_file({ path, old_string: oldString, new_string: newString });
}
Defensive patterns

Strategy: validation

Validate before calling

function assertEditDiffers(old_string: string, new_string: string): void {
  if (old_string === new_string) throw new Error("edit is a no-op; old_string equals new_string");
}

Prevention

When it happens

Trigger: Calling edit_file with identical old_string and new_string values — typically a scripted caller whose before/after variables collapsed to the same content, or a model emitting a placeholder edit.

Common situations: Code-generation loops producing degenerate edits; templating where the substitution failed and both placeholders rendered identically; refactoring scripts that computed new content equal to existing content.

Related errors


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