CherryHQ/cherry-studio · error · Error

old_string not found in content

Error message

old_string not found in content

What it means

Thrown by replaceWithFuzzyMatch when none of the nine replacers (Simple, LineTrimmed, BlockAnchor, WhitespaceNormalized, IndentationFlexible, EscapeNormalized, TrimmedBoundary, ContextAware, MultiOccurrence) yields any substring that content.indexOf can locate. The variable notFound stays true through the whole ALL_REPLACERS loop, meaning the old_string is absent from the content beyond what fuzzy matching can recover.

Source

Thrown at src/main/ai/mcp/servers/filesystem/types.ts:545

  let notFound = true

  for (const replacer of ALL_REPLACERS) {
    for (const search of replacer(content, oldString)) {
      const index = content.indexOf(search)
      if (index === -1) continue
      notFound = false
      if (replaceAll) {
        return content.replaceAll(search, newString)
      }
      const lastIndex = content.lastIndexOf(search)
      if (index !== lastIndex) continue
      return content.substring(0, index) + newString + content.substring(index + search.length)
    }
  }

  if (notFound) {
    throw new Error('old_string not found in content')
  }
  throw new Error(
    'Found multiple matches for old_string. Provide more surrounding lines in old_string to identify the correct match.'
  )
}

// ============================================================================
// Binary File Detection
// ============================================================================

// Check if a file is likely binary
export async function isBinaryFile(filePath: string): Promise<boolean> {
  try {
    const buffer = Buffer.alloc(4096)
    const fd = await fs.open(filePath, 'r')
    const { bytesRead } = await fd.read(buffer, 0, buffer.length, 0)
    await fd.close()

View on GitHub (pinned to 726446b54c)

Solutions

  1. Re-read the file to get current content, then copy old_string verbatim from that read.
  2. Normalize line endings (CRLF -> LF) on both content and old_string before editing.
  3. Shorten old_string to a unique anchor that is more likely to survive the fuzzy replacers, or expand it with exact surrounding context.
  4. Confirm you are editing the same file path that was read (no path mismatch).

Example fix

// before
replaceWithFuzzyMatch(staleContent, 'const x = 1;', 'const x = 2;') // throws: old_string not found

// after
const fresh = await fs.readFile(target, 'utf-8')
replaceWithFuzzyMatch(fresh, 'const x = 1;', 'const x = 2;')
Defensive patterns

Strategy: validation

Validate before calling

function canFind(content: string, oldString: string): boolean {
  // cheapest signal before invoking fuzzy matching
  return content.includes(oldString) ||
    content.split('\n').some(l => l.trim() === oldString.trim())
}

Try / catch

try {
  replaceWithFuzzyMatch(content, oldString, newString)
} catch (e) {
  if (e instanceof Error && e.message === 'old_string not found in content') {
    // re-read the file and rebuild old_string from the fresh content
  } else throw e
}

Prevention

When it happens

Trigger: Editing a file with old_string that no longer matches after the file changed on disk; old_string copied from a different file; content was reloaded from a stale cache; old_string includes lines that were already edited in a prior step of the same session.

Common situations: Multi-step edit where step 1 changed the region step 2 targets; LLM hallucinated old_string from memory rather than a fresh read; trailing whitespace/line-ending (CRLF vs LF) differences beyond what the normalization replacers cover (e.g. BOM, mixed encodings); file replaced wholesale between read and edit.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/2138d77b9b56a2e7. Report an issue: GitHub.