JuliusBrussee/caveman · error · Error

caveman-code: old_string not found in ${input.path}

Error message

caveman-code: old_string not found in ${input.path}

What it means

edit_file counts occurrences of old_string in the target file via split(); zero occurrences means the file on disk does not contain the exact text you supplied, so nothing can be replaced. The comparison is exact, including whitespace and indentation.

Source

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

      "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.
      const updated = content.split(input.old_string).join(input.new_string);
      await writeFile(target, updated, "utf8");
      const replaced = input.replace_all === true ? occurrences : 1;
      return capOutput(
        `edited ${input.path}: ${replaced} replacement${replaced === 1 ? "" : "s"}`,
        caps.edit_file,

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Re-read the file and copy old_string verbatim from current content (exact bytes, exact indentation)
  2. Check for line-ending drift: if the file is CRLF, your old_string must contain \r\n too (or normalize the file first)
  3. If the edit was already applied, verify the target text now matches new_string and skip the edit
  4. Shorten old_string to a unique snippet you can confirm exists rather than guessing at a large block

Example fix

// before: guessed whitespace
await edit_file({ path, old_string: "function run() {", new_string: "async function run() {" });

// after: read first, then edit with exact text
const cur = await read_file(path);
const anchor = cur.match(/function run\(\) \{/)[0]; // exact bytes from disk
await edit_file({ path, old_string: anchor, new_string: "async function run() {" });
Defensive patterns

Strategy: validation

Validate before calling

const content = await readFile(target, "utf8");
if (!content.includes(old_string)) {
  throw new Error(`anchor missing in ${target}; re-read the file and copy exact text`);
}

Try / catch

try {
  await editTool.execute(input);
} catch (err) {
  if (err instanceof Error && err.message.includes("old_string not found")) {
    // re-read the file, recompute the anchor from current content, retry once
    const fresh = await readFile(target, "utf8");
    const recomputed = deriveAnchor(fresh);
    if (recomputed === null) throw err;
    await editTool.execute({ ...input, old_string: recomputed });
  } else throw err;
}

Prevention

When it happens

Trigger: Calling edit_file where old_string has trailing-whitespace differences, tabs vs spaces, CRLF vs LF, was copied from a different file/branch, or the file was already edited and the anchor text no longer exists.

Common situations: Stale anchors after another edit already applied the change; line-ending mismatches on Windows checkouts; copies from documentation that normalized whitespace; concurrent edits by another process between read and edit.

Related errors


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