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

  1. Re-read the file (read_file) immediately before editing and copy oldText character-for-character from that output, including indentation.
  2. Make oldText the smallest unique snippet containing the change (a few lines) rather than a large block, reducing whitespace mismatch surface.
  3. If the file may have changed concurrently, re-read and regenerate the edit, or retry once after the failure.
  4. 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

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


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)