charmbracelet/crush · error

old_string appears multiple times in the file. Please provid

Error message

old_string appears multiple times in the file. Please provide more context to ensure a unique match, or set replace_all to true

What it means

findAndReplace requires old_string to occur exactly once in the file when replace_all is false. If strings.Index and strings.LastIndex disagree, the string occurs multiple times and this guard rejects the edit rather than making an ambiguous replacement. It is a deliberate safety check against unintended mass edits.

Source

Thrown at internal/agent/tools/edit.go:212

// findAndReplace performs a find-and-replace on content. When replaceAll is
// false it requires exactly one match. If an exact match fails, it falls back
// to whitespace-normalized matching and, failing that, returns a diagnostic
// hint describing why the replacement could not be made. The returned boolean
// reports whether the replacement relied on the whitespace-normalized
// fallback rather than an exact match.
func findAndReplace(content, old, new string, replaceAll bool) (string, bool, error) {
	if replaceAll {
		if strings.Contains(content, old) {
			return strings.ReplaceAll(content, old, new), false, nil
		}
	} else {
		index := strings.Index(content, old)
		switch {
		case index == -1:
			// Fall through to the fuzzy fallback below.
		case index != strings.LastIndex(content, old):
			return "", false, fmt.Errorf("old_string appears multiple times in the file. Please provide more context to ensure a unique match, or set replace_all to true")
		default:
			return content[:index] + new + content[index+len(old):], false, nil
		}
	}

	if result, ok := normalizedReplace(content, old, new, replaceAll); ok {
		return result, true, nil
	}
	return "", false, notFoundError(content, old)
}

// withWhitespaceNote appends the whitespace auto-correction note to a tool
// response message when the edit did not match the file byte-for-byte.
func withWhitespaceNote(message string, whitespaceCorrected bool) string {
	if !whitespaceCorrected {
		return message
	}
	return message + "\n" + whitespaceCorrectedNote

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Expand old_string to include surrounding unique context (more lines, distinctive tokens).
  2. Set replace_all to true if every occurrence should be replaced.
  3. Split the edit into multiple calls, each targeting one unique occurrence.
  4. Re-read the file (View tool) to copy exact text including indentation, then retry.

Example fix

// before
{"old_string": "return nil", "new_string": "return err"}
// after: include unique context
{"old_string": "if err != nil {\n\treturn nil\n}", "new_string": "if err != nil {\n\treturn err\n}", "replace_all": false}
Defensive patterns

Strategy: validation

Validate before calling

func isUniqueEnough(content, old string) bool {
	if strings.Count(content, old) != 1 {
		return false
	}
	return true
}
// gate the tool call:
// if !isUniqueEnough(fileContent, oldString) { set replace_all or extend context }

Try / catch

if strings.Contains(err.Error(), "appears multiple times") {
	// widen old_string with surrounding lines, or set replace_all: true, and retry
}

Prevention

When it happens

Trigger: Calling the edit tool with an old_string that matches 2+ locations in the file while replace_all is false — typically because the snippet is too short or generic (e.g. a closing brace, a repeated import line, an identifier alone on a line).

Common situations: Model-supplied edits using short common snippets, refactors where the same pattern appears in several functions, CRLF vs LF mismatches after whitespace normalization changing match counts.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/c62087d3d76d16e1. Report an issue: GitHub.