{"record":{"id":"de3b55c28126c42f","repo":"Mintplex-Labs/anything-llm","slug":"could-not-find-exact-match-for-edit-n-edit-oldte","errorCode":null,"errorMessage":"Could not find exact match for edit:\\n${edit.oldText}","messagePattern":"Could not find exact match for edit:\\\\n(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/utils/agents/aibitat/plugins/filesystem/lib.js","lineNumber":587,"sourceCode":"              const relativeIndent = newIndent.length - oldIndent.length;\n              return (\n                originalIndent +\n                \" \".repeat(Math.max(0, relativeIndent)) +\n                line.trimStart()\n              );\n            }\n            return line;\n          });\n\n          contentLines.splice(i, oldLines.length, ...newLines);\n          modifiedContent = contentLines.join(\"\\n\");\n          matchFound = true;\n          break;\n        }\n      }\n\n      if (!matchFound) {\n        throw new Error(\n          `Could not find exact match for edit:\\n${edit.oldText}`\n        );\n      }\n    }\n\n    const diffResult = this.#createUnifiedDiff(\n      content,\n      modifiedContent,\n      filePath\n    );\n\n    let numBackticks = 3;\n    while (diffResult.includes(\"`\".repeat(numBackticks))) {\n      numBackticks++;\n    }\n    const formattedDiff = `${\"`\".repeat(numBackticks)}diff\\n${diffResult}${\"`\".repeat(numBackticks)}\\n\\n`;\n\n    if (!dryRun) {","sourceCodeStart":569,"sourceCodeEnd":605,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/3aec848f2885144aa8f1e53b9731a04310d5d558/server/utils/agents/aibitat/plugins/filesystem/lib.js#L569-L605","documentation":"Thrown by applyFileEdits when an edit's oldText matches neither the exact substring nor the line-by-line trimmed-whitespace fallback in the current file content. The library first tries content.includes(oldText) after CRLF/LF normalization, then a per-line trim-equality scan; both failing means the text to replace is not present. The thrown message embeds the unmatched oldText so you can diff it against the real file.","triggerScenarios":"Calling the edit_file/str_replace tool with oldText whose indentation, quoting, or whitespace differs from the file; editing a file that changed since it was last read (stale content); oldText spanning a different set of lines than the actual file; non-breaking vs regular spaces copied from rendered output.","commonSituations":"LLM agent hallucinates or approximates the snippet instead of copying it verbatim from a prior read; two concurrent edits race so the second edit's oldText no longer exists; CRLF/encoding differences between what read_file returned and what the model echoed back.","solutions":["Re-read the file (read_file) immediately before editing and copy oldText character-for-character from that output, including indentation.","Make oldText the smallest unique snippet containing the change (a few lines) rather than a large block, reducing whitespace mismatch surface.","If the file may have changed concurrently, re-read and regenerate the edit, or retry once after the failure.","Check for CRLF files and tabs-vs-spaces; the matcher normalizes line endings but the fallback only ignores leading/trailing whitespace per line, not internal spacing."],"exampleFix":"// before: approximate snippet\nedit_file({ path: \"app.js\", edits: [{ oldText: \"function main() {\", newText: \"async function main() {\" }] })\n// file actually contains \"export function main() {\" -> throws\n\n// after: exact snippet copied from read_file output\nedit_file({ path: \"app.js\", edits: [{ oldText: \"export function main() {\", newText: \"export async function main() {\" }] })","handlingStrategy":"validation","validationCode":"const fs = require(\"fs\").promises;\nconst normalize = (s) => s.replace(/\\r\\n/g, \"\\n\");\nasync function verifyEditsApply(filePath, edits) {\n  const content = normalize(await fs.readFile(filePath, \"utf8\"));\n  for (const edit of edits) {\n    if (!content.includes(normalize(edit.oldText))) {\n      throw new Error(`oldText not present in ${filePath} — re-read the file and copy the snippet verbatim`);\n    }\n  }\n}","typeGuard":"/** @returns {boolean} true when every edit's oldText exists in content */\nfunction editsAreApplicable(content, edits) {\n  const c = content.replace(/\\r\\n/g, \"\\n\");\n  return edits.every((e) => typeof e?.oldText === \"string\" && c.includes(e.oldText.replace(/\\r\\n/g, \"\\n\")));\n}","tryCatchPattern":"try {\n  await fileOps.applyFileEdits(filePath, edits);\n} catch (e) {\n  if (e.message.startsWith(\"Could not find exact match for edit:\")) {\n    const fresh = await fs.readFile(filePath, \"utf8\");\n    // rebuild edits from actual snippets in `fresh`, then retry exactly once\n  } else throw e;\n}","preventionTips":["Always read_file immediately before edit_file and copy oldText verbatim, including indentation.","Keep oldText snippets small and unique (3-5 lines around the change).","Serialize concurrent edits to the same file to avoid stale-content mismatches.","Remember line endings are normalized but internal whitespace is not."],"tags":["filesystem","edit","string-matching","agent-tools","llm-agents"],"backgroundTag":"search-replace-no-match","analyzedSha":"3aec848f2885144aa8f1e53b9731a04310d5d558","analyzedAt":"2026-08-18T10:02:21.017Z","contentChangedAt":"2026-08-18T10:02:21.017Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}