vxcontrol/pentagi · error

%d of %d hunk(s) could not be applied - read the file again

Error message

%d of %d hunk(s) could not be applied - read the file again and retry with context that matches its current content exactly:
%s

What it means

diff-match-patch applied the patch, but one or more hunks did not find their context in the current file content. The error lists which hunk headers failed and a preview of the text each hunk expected to find, instructing the caller to re-read the file and retry with exact context. This is the expected failure mode for diffs built against a stale version of the file.

Source

Thrown at backend/pkg/tools/file_diff.go:315

	patchText := buildGoDiffPatchText(hunks, content)

	dmp := diffmatchpatch.New()
	patches, err := dmp.PatchFromText(patchText)
	if err != nil {
		return "", 0, fmt.Errorf("internal error building patch: %w", err)
	}

	newContent, applied := dmp.PatchApply(patches, content)

	var failed []string
	for i, ok := range applied {
		if !ok && i < len(hunks) {
			failed = append(failed, fmt.Sprintf("%s (not found in the file, looked for: %q)", hunks[i].header, hunkOldPreview(hunks[i])))
		}
	}
	if len(failed) > 0 {
		return "", 0, fmt.Errorf(
			"%d of %d hunk(s) could not be applied - read the file again and retry with context that matches its current content exactly:\n%s",
			len(failed), len(hunks), strings.Join(failed, "\n"),
		)
	}

	return newContent, len(hunks), nil
}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Re-read the file from disk and regenerate the diff using its current exact content as context.
  2. Copy context lines verbatim from the file, including trailing whitespace, instead of retyping them.
  3. Reduce the amount of context to the minimum needed to be unique, lessening the chance of mismatch.
  4. Normalize line endings (match the file's CRLF/LF convention) before applying.
  5. Retry after confirming no other process is concurrently modifying the file.

Example fix

// before: context invented by the model
"@@ -3,2 +3,3 @@\n some old line\n+added"

// after: context copied exactly from a fresh file read
"@@ -3,2 +3,3 @@\n someOldLine := compute(x) // exact current line\n+added"
Defensive patterns

Strategy: retry

Validate before calling

func contextMatches(content, ctxLine string) bool {
	return strings.Contains(content, strings.TrimPrefix(ctxLine, " "))
}
// verify every context/removal line appears in freshly read content before applying

Try / catch

for attempt := 0; attempt < 3; attempt++ {
	content, _ := readFile(path) // fresh read each attempt
	diff := buildDiffAgainst(content)
	newContent, _, err := ApplyUnifiedDiff(ctx, path, diff)
	if err == nil { return newContent, nil }
	if !strings.Contains(err.Error(), "could not be applied") { return err }
}

Prevention

When it happens

Trigger: EditFile/ApplyUnifiedDiff called with a diff whose context lines do not byte-match the file's current content — file changed since the diff was made, line numbers drifted, trailing whitespace or line endings differ, or context was paraphrased by an LLM instead of copied.

Common situations: Concurrent edits between reading the file and applying the diff; agent generated context from memory rather than an actual file read; CRLF vs LF mismatch; context lines abbreviated with '...' or re-wrapped by chat rendering.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/58d2aefbc6669295. Report an issue: GitHub.