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
- Generate the diff with real tooling (`diff -u old new` or `git diff`) rather than asking the model to hand-write hunks
- Validate the diff format before calling: it must contain `---`, `+++`, and at least one `@@ ... @@` hunk header
- Strip markdown fences and normalize line endings (\n) from model output before passing diffText
- Re-read the file to get current content and regenerate the diff against it if it may be stale
- 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
- Generate diffs with diff -u / git diff, never hand-written hunks
- Strip markdown fences and CRLF from model-produced diffs
- Regenerate the diff against freshly read content if it may be stale
- Fall back to WriteFile when diff application fails repeatedly
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
- diff is required and cannot be empty
- failed to parse template: %w
- diff contains no hunks (no "@@ ... @@" header found)
- invalid hunk header: %q
- invalid hunk header %q: %w
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/90ecb4ed243d0961.
Report an issue: GitHub.