vxcontrol/pentagi · error

internal error building patch: %w

Error message

internal error building patch: %w

What it means

After parsing the hunks, ApplyUnifiedDiff re-serializes them via buildGoDiffPatchText and feeds the result to diff-match-patch's PatchFromText. If dmp cannot parse the internally generated patch text, this is treated as a bug in the tool itself ('internal error'), not bad user input — the parsed hunks were valid but the reconstruction produced text diff-match-patch rejects.

Source

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

// and the number of hunks applied, or a descriptive error naming every hunk
// that failed to apply and a preview of the content it looked for -
// content is returned unchanged (empty) on error, so a partial/bad diff
// never corrupts the file. Exported so other packages (e.g. the provider
// tester) can exercise the exact production diff-merge semantics without
// going through EditFile's Docker-backed read/write.
func ApplyUnifiedDiff(content, diffText string) (string, int, error) {
	hunks, err := parseUnifiedDiff(diffText)
	if err != nil {
		return "", 0, err
	}
	hunks = ensureContextBoundaries(hunks, content)

	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. This is an internal invariant failure — report it with the diff input and target file that triggered it.
  2. Retry the edit with a smaller, simpler diff (fewer hunks, ASCII-only context) as a workaround.
  3. Sanitize the target file to clean UTF-8 without control characters, then re-read it and re-apply.
  4. Check the diff-match-patch dependency version for known PatchFromText regressions in go.mod.

Example fix

// workaround: split one large multi-hunk edit into smaller sequential edits
// before: ApplyUnifiedDiff(ctx, path, bigDiffWith12Hunks)
// after:  ApplyUnifiedDiff(ctx, path, hunk1Diff); ApplyUnifiedDiff(ctx, path, hunk2Diff)
Defensive patterns

Strategy: fallback

Validate before calling

func sanitizeForDiff(s string) string {
	return strings.Map(func(r rune) rune {
		if r == '\n' || r == '\t' || (r >= 0x20 && r != 0x7f) { return r }
		return -1 // drop control chars dmp may choke on
	}, s)
}

Try / catch

newContent, n, err := ApplyUnifiedDiff(ctx, path, diff)
if err != nil && strings.Contains(err.Error(), "internal error building patch") {
	// fallback: rewrite file via read-modify-write instead of patching
	return rewriteFileManually(ctx, path, hunks)
}

Prevention

When it happens

Trigger: buildGoDiffPatchText emits characters or encodings diff-match-patch's strict PatchFromText grammar rejects — typically due to unusual content in hunk lines (control characters, malformed URL-encoded indices, edge cases around empty lines or very large offsets).

Common situations: Diffing binary-ish or non-UTF8 file content; hunk text containing raw control bytes; rare dmp library version incompatibilities with the generated patch format; files with mixed line endings confusing the reconstruction.

Related errors


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