charmbracelet/crush · error

edit %d: only the first edit can have empty old_string (for

Error message

edit %d: only the first edit can have empty old_string (for file creation)

What it means

validateEdits enforces the multi-edit file-creation convention: an empty old_string means 'create the file', which is only meaningful for the first edit. If any edit after the first has an empty old_string, validation fails with the 1-based edit index so the model/user can correct the call. No edits are applied when this fires.

Source

Thrown at internal/agent/tools/multiedit.go:120

			}

			// Notify LSP clients about the change
			notifyLSPs(ctx, lspManager, params.FilePath)

			// Wait for LSP diagnostics and add them to the response
			text := fmt.Sprintf("<result>\n%s\n</result>\n", response.Content)
			text += getDiagnostics(params.FilePath, lspManager)
			response.Content = text
			return response, nil
		},
	)
}

func validateEdits(edits []MultiEditOperation) error {
	for i, edit := range edits {
		// Only the first edit can have empty old_string (for file creation)
		if i > 0 && edit.OldString == "" {
			return fmt.Errorf("edit %d: only the first edit can have empty old_string (for file creation)", i+1)
		}
	}
	return nil
}

// applyEditsToContent applies edits sequentially, collecting the ones that
// failed. It also reports whether any edit only matched after whitespace
// normalization.
func applyEditsToContent(currentContent string, edits []MultiEditOperation, startIndex int) (string, []FailedEdit, bool) {
	var failedEdits []FailedEdit
	var whitespaceCorrected bool
	for i, edit := range edits {
		newContent, corrected, err := applyEditToContent(currentContent, edit)
		if err != nil {
			failedEdits = append(failedEdits, FailedEdit{
				Index: startIndex + i + 1,
				Error: err.Error(),
				Edit:  edit,

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Ensure exactly one edit has empty old_string and it is the first element of the edits array
  2. For inserting content into an existing file, provide the surrounding old_string to replace rather than an empty one
  3. Move the file-creation edit to position 0 and apply subsequent edits after it
  4. If the file already exists, drop the empty-old_string edit and use normal search/replace edits

Example fix

// before
edits := []MultiEditOperation{
  {OldString: "foo", NewString: "bar"},
  {OldString: "", NewString: "created"}, // invalid: not first
}
// after
edits := []MultiEditOperation{
  {OldString: "", NewString: "created"}, // creation first
  {OldString: "foo", NewString: "bar"},
}
Defensive patterns

Strategy: validation

Validate before calling

// mirror the library's rule before issuing the call
for i, e := range edits {
    if i > 0 && e.OldString == "" {
        return fmt.Errorf("edit %d cannot have empty old_string", i+1)
    }
}

Type guard

func isCreationEdit(i int, e MultiEditOperation) bool {
    return i == 0 && e.OldString == ""
}

Prevention

When it happens

Trigger: A multi_edit tool call where edits[1:], not edits[0], contains an operation with old_string == "" — typically a model-generated edit list where a replacement entry has an empty search string, or a creation entry misplaced after other edits.

Common situations: LLM omits old_string thinking it means 'insert here'; edit list reordered so the creation edit is not first; tool-call serialization dropping old_string fields that were empty.

Related errors


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