{"record":{"id":"4f290fcbdaba7f89","repo":"charmbracelet/crush","slug":"old-string-cannot-be-empty-for-content-replacement","errorCode":null,"errorMessage":"old_string cannot be empty for content replacement","messagePattern":"old_string cannot be empty for content replacement","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/tools/multiedit.go","lineNumber":358,"sourceCode":"\t\t\tOldContent:   oldContent,\n\t\t\tNewContent:   currentContent,\n\t\t\tAdditions:    additions,\n\t\t\tRemovals:     removals,\n\t\t\tEditsApplied: editsApplied,\n\t\t\tEditsFailed:  failedEdits,\n\t\t},\n\t), nil\n}\n\n// applyEditToContent applies a single edit, reporting whether it only matched\n// after whitespace normalization.\nfunc applyEditToContent(content string, edit MultiEditOperation) (string, bool, error) {\n\tif edit.OldString == \"\" && edit.NewString == \"\" {\n\t\treturn content, false, nil\n\t}\n\n\tif edit.OldString == \"\" {\n\t\treturn \"\", false, fmt.Errorf(\"old_string cannot be empty for content replacement\")\n\t}\n\n\treturn findAndReplace(content, edit.OldString, edit.NewString, edit.ReplaceAll)\n}\n","sourceCodeStart":340,"sourceCodeEnd":363,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/agent/tools/multiedit.go#L340-L363","documentation":"applyEditToContent rejects an edit whose old_string is empty but new_string is not. Empty old_string would mean 'replace everything/nothing' and is ambiguous, so instead of guessing the tool fails the operation. Both-empty edits are treated as no-ops and pass silently.","triggerScenarios":"An edit operation in the edits array has old_string == \"\" while new_string is non-empty. This fails that single edit; other edits in the MultiEdit call may still apply (partial success).","commonSituations":"LLM emitting an insertion-style edit (old_string empty intending 'insert here') which this tool does not support; serialization dropping old_string because it was falsy; prompt/model version that uses a different replace semantics.","solutions":["Provide the exact existing text to replace in old_string; there is no empty-string insertion mode","To insert content, include the anchor line in both old_string and new_string","To clear a file, use the write/edit tool instead of MultiEdit","Check the edits array in the tool input for entries missing old_string"],"exampleFix":"// before\n{\"old_string\": \"\", \"new_string\": \"func main() {}\"}\n// after\n{\"old_string\": \"package main\\n\", \"new_string\": \"package main\\n\\nfunc main() {}\\n\"}","handlingStrategy":"validation","validationCode":"for i, e := range edits {\n    if e.NewString != \"\" && e.OldString == \"\" {\n        return fmt.Errorf(\"edit %d: old_string required when new_string is set\", i)\n    }\n}","typeGuard":null,"tryCatchPattern":"if _, _, err := applyEditsToContent(content, edits, 0); err != nil {\n    // inspect which operation had empty old_string and rebuild it with real context text\n}","preventionTips":["Always supply the exact text to replace in old_string","For insertions, anchor on an existing line in old_string","Validate tool-call JSON before dispatching to MultiEdit","Use the dedicated write tool for whole-file content changes"],"tags":["validation","tool-input","multiedit"],"backgroundTag":"empty-search-string","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}