vxcontrol/pentagi · error

failed to apply diff to %s: %w

Error message

failed to apply diff to %s: %w

What it means

After reading the current content, EditFile runs ApplyUnifiedDiff to apply the hunks in memory. This error wraps ApplyUnifiedDiff returning a parse or apply error — malformed diff format (missing ---/+++ headers, bad @@ hunk headers, corrupt line offsets) rather than a context-mismatch (hunks that simply don't match usually yield newContent with fewer hunks applied, not an error). No file modification occurs.

Source

Thrown at backend/pkg/tools/terminal.go:517

// content, applies the diff to it entirely in memory (see applyUnifiedDiff),
// and only if every hunk applied cleanly writes the result back - a diff
// that doesn't fully apply leaves the file untouched.
func (t *terminal) EditFile(ctx context.Context, flowID int64, path, diffText string) (string, error) {
	if path == "" {
		return "", fmt.Errorf("path is required and cannot be empty")
	}
	if strings.TrimSpace(diffText) == "" {
		return "", fmt.Errorf("diff is required and cannot be empty")
	}

	current, err := t.readFileFromContainer(ctx, flowID, path)
	if err != nil {
		return "", fmt.Errorf("failed to read current content of %s before editing: %w", path, err)
	}

	newContent, hunksApplied, err := ApplyUnifiedDiff(current, diffText)
	if err != nil {
		return "", fmt.Errorf("failed to apply diff to %s: %w", path, err)
	}

	if err := t.writeFileToContainer(ctx, flowID, path, newContent); err != nil {
		return "", fmt.Errorf("failed to write edited content of %s: %w", path, err)
	}

	successMsg := fmt.Sprintf("Applied %d diff hunk(s) to %s (%d -> %d bytes)", hunksApplied, path, len(current), len(newContent))
	styledMsg := fmt.Sprintf("%s%s%s%s", ansiColorSystemMsg, successMsg, ansiColorReset, ansiLineTerminator)
	if _, err := t.tlp.PutMsg(ctx, database.TermlogTypeStdin, styledMsg, t.containerID, t.taskID, t.subtaskID); err != nil {
		return "", fmt.Errorf("failed to put terminal log (edit file cmd): %w", err)
	}

	return successMsg, nil
}

// PrimaryTerminalName returns the docker container name for a flow's primary
// terminal, namespaced by the configured tenant.
//

View on GitHub (pinned to ea665308ba)

Solutions

  1. Generate the diff with real tooling (`diff -u old new` or `git diff`) rather than asking the model to hand-write hunks
  2. Validate the diff format before calling: it must contain `---`, `+++`, and at least one `@@ ... @@` hunk header
  3. Strip markdown fences and normalize line endings (\n) from model output before passing diffText
  4. Re-read the file to get current content and regenerate the diff against it if it may be stale
  5. Fall back to WriteFile with the full desired content when diff application repeatedly fails

Example fix

// before
const diff = "```diff\n--- a/f.txt\n+++ b/f.txt\n...\n```" // fenced, may carry \r\n
msg, err := term.EditFile(ctx, flowID, path, diff)
// after
cleaned := strings.NewReplacer("```diff", "", "```", "", "\r\n", "\n").Replace(diff)
if !strings.Contains(cleaned, "---") || !strings.Contains(cleaned, "@@") {
    return fmt.Errorf("invalid unified diff for %s: missing headers/hunk markers", path)
}
msg, err := term.EditFile(ctx, flowID, path, cleaned)
Defensive patterns

Strategy: validation

Validate before calling

func validUnifiedDiff(d string) bool {
    return strings.Contains(d, "---") && strings.Contains(d, "+++") && strings.Contains(d, "@@")
}
// use: if !validUnifiedDiff(diffText) { regenerate via `diff -u` before calling }

Try / catch

msg, err := term.EditFile(ctx, flowID, path, diff)
if err != nil && strings.Contains(err.Error(), "failed to apply diff") {
    // fall back to full-content write
    _, werr := term.WriteFile(ctx, flowID, desiredFullContent, path)
    _ = werr
}

Prevention

When it happens

Trigger: Passing a diffText that is not a well-formed unified diff: missing file headers, malformed @@ start,count @@ lines, an empty hunk body, or a truncated diff; passing an entirely different diff format (git's --patch with extra metadata the parser rejects, or context-free diffs when the parser requires context lines).

Common situations: LLM agents producing hand-written pseudo-diffs instead of real `diff -u` output; diffs copied with CRLF line endings or markdown code-fence artifacts; diffs generated against different file content than what is in the container (stale read); truncated diffs from token limits.

Related errors


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