Mintplex-Labs/anything-llm · error
Could not find exact match for edit:\n
Error message
Could not find exact match for edit:\n${edit.oldText} What it means
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.
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.
Example fix
// before: approximate snippet
edit_file({ path: "app.js", edits: [{ oldText: "function main() {", newText: "async function main() {" }] })
// file actually contains "export function main() {" -> throws
// after: exact snippet copied from read_file output
edit_file({ path: "app.js", edits: [{ oldText: "export function main() {", newText: "export async function main() {" }] }) Defensive patterns
Strategy: validation
Validate before calling
const fs = require("fs").promises;
const normalize = (s) => s.replace(/\r\n/g, "\n");
async function verifyEditsApply(filePath, edits) {
const content = normalize(await fs.readFile(filePath, "utf8"));
for (const edit of edits) {
if (!content.includes(normalize(edit.oldText))) {
throw new Error(`oldText not present in ${filePath} — re-read the file and copy the snippet verbatim`);
}
}
} Type guard
/** @returns {boolean} true when every edit's oldText exists in content */
function editsAreApplicable(content, edits) {
const c = content.replace(/\r\n/g, "\n");
return edits.every((e) => typeof e?.oldText === "string" && c.includes(e.oldText.replace(/\r\n/g, "\n")));
} Try / catch
try {
await fileOps.applyFileEdits(filePath, edits);
} catch (e) {
if (e.message.startsWith("Could not find exact match for edit:")) {
const fresh = await fs.readFile(filePath, "utf8");
// rebuild edits from actual snippets in `fresh`, then retry exactly once
} else throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Access denied - path outside allowed directories.
- Parent directory does not exist
- Access denied - parent directory outside allowed…
- Access denied - symlink target outside allowed directories.
- Cannot copy symbolic link
AI-assisted analysis of Mintplex-Labs/anything-llm@3aec848f28 (2026-08-18).
Data as JSON: /api/errors/de3b55c28126c42f.
Report an issue: GitHub.
Appendix: source
Thrown at server/utils/agents/aibitat/plugins/filesystem/lib.js:587
const relativeIndent = newIndent.length - oldIndent.length;
return (
originalIndent +
" ".repeat(Math.max(0, relativeIndent)) +
line.trimStart()
);
}
return line;
});
contentLines.splice(i, oldLines.length, ...newLines);
modifiedContent = contentLines.join("\n");
matchFound = true;
break;
}
}
if (!matchFound) {
throw new Error(
`Could not find exact match for edit:\n${edit.oldText}`
);
}
}
const diffResult = this.#createUnifiedDiff(
content,
modifiedContent,
filePath
);
let numBackticks = 3;
while (diffResult.includes("`".repeat(numBackticks))) {
numBackticks++;
}
const formattedDiff = `${"`".repeat(numBackticks)}diff\n${diffResult}${"`".repeat(numBackticks)}\n\n`;
if (!dryRun) {View on GitHub (pinned to 3aec848f28)