CherryHQ/cherry-studio · error · Error
old_string and new_string must be different
Error message
old_string and new_string must be different
What it means
Thrown by replaceWithFuzzyMatch (types.ts:518) when oldString === newString. The function performs an edit, so an identical old/new pair is a no-op and is rejected upfront as a caller logic error before any replacer runs. This guards the edit tool against accidental no-op edits that would otherwise silently succeed.
Source
Thrown at src/main/ai/mcp/servers/filesystem/types.ts:525
WhitespaceNormalizedReplacer,
IndentationFlexibleReplacer,
EscapeNormalizedReplacer,
TrimmedBoundaryReplacer,
ContextAwareReplacer,
MultiOccurrenceReplacer
]
/**
* Replace oldString with newString in content using fuzzy matching
*/
export function replaceWithFuzzyMatch(
content: string,
oldString: string,
newString: string,
replaceAll = false
): string {
if (oldString === newString) {
throw new Error('old_string and new_string must be different')
}
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)
}
}
View on GitHub (pinned to 726446b54c)
Solutions
- Make new_string actually differ from old_string (even a single character change is enough).
- If you only want to verify content exists, use a search/read tool instead of edit.
- Add an assertion old !== new in the caller before invoking edit to fail fast with your own message.
Example fix
// before replaceWithFuzzyMatch(content, 'const x = 1', 'const x = 1') // throws: old_string and new_string must be different // after replaceWithFuzzyMatch(content, 'const x = 1', 'const x = 2')
Defensive patterns
Strategy: validation
Validate before calling
function assertDifferentEdit(oldString: string, newString: string): void {
if (oldString === newString) {
throw new Error('Refusing no-op edit: old_string equals new_string')
}
} Type guard
function isMeaningfulEdit(oldString: string, newString: string): boolean {
return oldString !== newString
} Try / catch
try {
replaceWithFuzzyMatch(content, oldString, newString)
} catch (e) {
if (e instanceof Error && e.message === 'old_string and new_string must be different') {
// nothing to do; skip the edit entirely
} else throw e
} Prevention
- Construct new_string by transforming old_string, then assert they differ before sending.
- For verification-only flows, use read/search rather than edit.
- Beware auto-normalizers (trim, line-ending) that can equalize both sides.
When it happens
Trigger: Calling the edit tool (or replaceWithFuzzyMatch directly) with old_string and new_string that are byte-identical. Common when both are templated from the same source, copy-pasted, or when the caller intends only a whitespace change but normalizes both sides identically first.
Common situations: Caller copies the selection into both old and new fields then forgets to edit the new field; a refactor script derives new from old with no transformation; trailing-newline normalization makes both strings equal; an LLM agent emits the same block twice.
Related errors
- old_string not found in content
- Found multiple matches for old_string. Provide more surround
- Path is not a file: ${filePath}
- File not found: ${filePath}
- Cannot read binary file: ${filePath}
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/e82bde49eef264b7.
Report an issue: GitHub.