{"record":{"id":"ccc4a4d095e9bdb4","repo":"JuliusBrussee/caveman","slug":"caveman-code-old-string-not-found-in-input-path","errorCode":null,"errorMessage":"caveman-code: old_string not found in ${input.path}","messagePattern":"caveman-code: old_string not found in (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/code.ts","lineNumber":349,"sourceCode":"      \"once unless replace_all is set. Writes to disk.\",\n    input: schema.object({\n      path: schema.string(),\n      old_string: schema.string(),\n      new_string: schema.string(),\n      replace_all: schema.optional(schema.boolean()),\n    }),\n    effect: \"write\",\n    result: \"inline\",\n    timeoutMs: READ_TIMEOUT_MS,\n    async execute(input) {\n      if (input.old_string === input.new_string) {\n        throw new Error(\"caveman-code: old_string and new_string are identical\");\n      }\n      const target = await contained(input.path);\n      const content = await readFile(target, \"utf8\");\n      const occurrences = content.split(input.old_string).length - 1;\n      if (occurrences === 0) {\n        throw new Error(`caveman-code: old_string not found in ${input.path}`);\n      }\n      if (occurrences > 1 && input.replace_all !== true) {\n        throw new Error(\n          `caveman-code: old_string appears ${occurrences} times in ${input.path}; ` +\n          \"add surrounding context or pass replace_all\",\n        );\n      }\n      // split/join UNCONDITIONALLY. String.prototype.replace\n      // interprets `$&`, `$\\``, `$'`, `$$`, `$1`… in the REPLACEMENT even for a\n      // string pattern, so a new_string containing any of them would silently\n      // corrupt the file. The non-replace_all branch is guaranteed exactly one\n      // occurrence above, so joining replaces precisely that one.\n      const updated = content.split(input.old_string).join(input.new_string);\n      await writeFile(target, updated, \"utf8\");\n      const replaced = input.replace_all === true ? occurrences : 1;\n      return capOutput(\n        `edited ${input.path}: ${replaced} replacement${replaced === 1 ? \"\" : \"s\"}`,\n        caps.edit_file,","sourceCodeStart":331,"sourceCodeEnd":367,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/code.ts#L331-L367","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-read the file and copy old_string verbatim from current content (exact bytes, exact indentation)","Check for line-ending drift: if the file is CRLF, your old_string must contain \\r\\n too (or normalize the file first)","If the edit was already applied, verify the target text now matches new_string and skip the edit","Shorten old_string to a unique snippet you can confirm exists rather than guessing at a large block"],"exampleFix":"// before: guessed whitespace\nawait edit_file({ path, old_string: \"function run() {\", new_string: \"async function run() {\" });\n\n// after: read first, then edit with exact text\nconst cur = await read_file(path);\nconst anchor = cur.match(/function run\\(\\) \\{/)[0]; // exact bytes from disk\nawait edit_file({ path, old_string: anchor, new_string: \"async function run() {\" });","handlingStrategy":"validation","validationCode":"const content = await readFile(target, \"utf8\");\nif (!content.includes(old_string)) {\n  throw new Error(`anchor missing in ${target}; re-read the file and copy exact text`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await editTool.execute(input);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"old_string not found\")) {\n    // re-read the file, recompute the anchor from current content, retry once\n    const fresh = await readFile(target, \"utf8\");\n    const recomputed = deriveAnchor(fresh);\n    if (recomputed === null) throw err;\n    await editTool.execute({ ...input, old_string: recomputed });\n  } else throw err;\n}","preventionTips":["Always read the file in the same turn you edit it; never reuse anchors from earlier reads","Normalize line endings knowledge: know whether the file is LF or CRLF before building anchors","Treat 'not found' as a signal your view of the file is stale, not as noise"],"tags":["edit","tool-input","mismatch","whitespace"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}