{"slug":"unified-diff-repair","name":"Unified Diff Repair: Fix the Counts, Keep the Change","version":"1.0.0","updated_at":"2026-10-08T19:41:26.004Z","use_when":"Repairs a unified diff (the patch format of git diff and diff -u) so that git apply and patch accept it against the file it was written for, and answers with the diff only, no code fence. Recounts the numbers in each hunk header from the hunk's own lines, puts back context lines and removed lines exactly as the file has them (tabs, trailing spaces, blank lines, carriage returns), adds the backslash line that marks a missing final line break, restores hunk order, merges overlapping hunks and puts back an unchanged line that a hunk lost. Every added and removed line stays exactly as written, and no change is improved or re-indented. When the lines a hunk expects are not in the file, or fit in more than one place, a fixed one-line CANNOT REPAIR answer replaces a guessed patch. Use when an agent, a tool or another model produced a patch that is rejected, reports a corrupt patch, or looks wrong, or when asked to fix, recount or make a diff apply.","not_for":"Merging branches, resolving conflicts, writing a diff from two files or judging a change. Context diffs, ed scripts and binary patches are out. A hunk whose context or removed lines are not in the file, or fit in two places, gets a CANNOT REPAIR line, not a guess. The file the diff is for must be given. Rules follow GNU diffutils and git.","languages":["any"],"tags":["unified-diff","patch","git-apply","hunk-header","diff-repair","coding-agent","no-guessing"],"category":"code","category_url":"https://aiskills402.com/categories/code","keywords":["corrupt patch","git apply","hunk header"],"faq":[{"q":"Headers, whitespace, markers: which parts change, and which never do?","a":"It changes the bookkeeping: line counts and start numbers in each hunk header, whitespace that the diff lost in transit (a tab turned into spaces, a trailing space, a carriage return, a blank context line without its space), the backslash marker for a missing final line break, hunk order and overlapping hunks. It never touches an added or a removed line: no re-indenting, no typo fixes, no reformatting, no reordering of the edits themselves. Usual culprits are automated summarizers, models that rewrite a patch, chat copy-paste, mail clients, tab-converting editors. The result applies cleanly."},{"q":"Why does a hunk that cannot be placed get a refusal and not a best-effort patch?","a":"Because a diff written for another version of the file has no correct repair. Dropping the hunk or changing the line it removes produces a patch that applies and does a different job, and nobody sees the difference until the code runs. The same holds when the context and removed lines fit in two places and the line number does not choose. The skill answers with one CANNOT REPAIR line that names the hunk, so a person or a better source of the file decides."},{"q":"Does it help Claude Sonnet?","a":"For Sonnet the gain was zero. Twenty-three broken diffs went to Sonnet and Haiku with and without the file, and each answer was applied to its file by our patch code. Sonnet repaired 22 either way. Haiku went from 18 to 22, which is where the file earns its two cents. One honest gap: a file with Windows line endings failed for every model on both sides, so we make no claim about carriage returns, even though the rules cover them."},{"q":"What do I need to give it?","a":"Two things in one message: the full text of the file the diff is for, exactly as it is now, and the broken diff. Without the file there is nothing to compare the context and the removed lines with, and the skill answers that it cannot repair a diff it cannot place. If the file is long, send it whole anyway: the position of a hunk is decided by finding its lines in the file, not by trusting the numbers in the header. A diff produced for an older revision of the file is the usual reason for a refusal; fetch the revision it was made against, or regenerate the patch, and run the skill again."}],"examples":[{"lang":"en","model":"claude-sonnet-5-5","input_excerpt":"<<<FILE\nconst express = require(\"express\");\nconst app = express();\nconst PORT = process.env.PORT || 3000;\n\napp.use(express.json());\napp.use(express.static(\"public\"));\n\nfunction health(req, res) {\n  res.json({ ok: true });\n}\n\napp.get(\"/health\", health);\napp.get(\"/version\", (req, res) => {\n  res.json({ version: \"1.4.0\" });\n});\n\napp.listen(PORT, () => {\n  console.log(\"listening on \" + PORT);\n});…","output_excerpt":"--- a/server.js\n+++ b/server.js\n@@ -11,7 +11,8 @@\n \n app.get(\"/health\", health);\n app.get(\"/version\", (req, res) => {\n-  res.json({ version: \"1.4.0\" });\n+  res.set(\"Cache-Control\", \"no-store\");\n+  res.json({ version: \"1.5.0\" });\n });\n \n app.listen(PORT, () => {"},{"lang":"bg","model":"claude-sonnet-5-5","input_excerpt":"<<<FILE\nПърва глава\n\nИмало едно време един цар.\nТой имал три дъщери.\n\nНай-малката била най-красива.\n\nВтора глава\nТя тръгнала на път.\n>>>FILE\n\n<<<DIFF\n--- a/prikazka.txt\n+++ b/prikazka.txt\n@@ -1,7 +1,7 @@\n Първа глава\n\n Имало едно време един цар.\n-Той имал три дъщери.\n+Той имал три сина.\n\n Най-малката била най-красива.\n\n>>>DIFF","output_excerpt":"--- a/prikazka.txt\n+++ b/prikazka.txt\n@@ -1,7 +1,7 @@\n Първа глава\n \n Имало едно време един цар.\n-Той имал три дъщери.\n+Той имал три сина.\n \n Най-малката била най-красива."},{"lang":"es","model":"claude-sonnet-5-5","input_excerpt":"<<<FILE\nHola\nGracias por su compra.\nSu pedido llegará el lunes.\nAtentamente\nLa tienda\n>>>FILE\n\n<<<DIFF\nAquí tienes el parche:\n\n```diff\n--- a/correo.txt\n+++ b/correo.txt\n@@ -1,5 +1,5 @@\n Hola\n Gracias por su compra.\n-Su pedido llegará el lunes.\n+Su pedido llegará el martes.\n Atentamente\n La tienda\n```\n\nSaludos.\n>>>DIFF","output_excerpt":"--- a/correo.txt\n+++ b/correo.txt\n@@ -1,5 +1,5 @@\n Hola\n Gracias por su compra.\n-Su pedido llegará el lunes.\n+Su pedido llegará el martes.\n Atentamente\n La tienda"},{"lang":"de","model":"claude-sonnet-5-5","input_excerpt":"<<<FILE\nWillkommen\nBitte melden Sie sich an.\nPasswort vergessen?\nImpressum\nDatenschutz\n>>>FILE\n\n<<<DIFF\ndiff --git a/de.txt b/de.txt\nindex 83db48f..bf2a1c3 100644\n--- a/de.txt\n+++ b/de.txt\n@@ -1,5 +1,5 @@\n Willkommen\n Bitte melden Sie sich an.\n-Passwort vergessen?\n+Passwort zurücksetzen\n Impressum\n Datenschutz\n>>>DIFF","output_excerpt":"diff --git a/de.txt b/de.txt\nindex 83db48f..bf2a1c3 100644\n--- a/de.txt\n+++ b/de.txt\n@@ -1,5 +1,5 @@\n Willkommen\n Bitte melden Sie sich an.\n-Passwort vergessen?\n+Passwort zurücksetzen\n Impressum\n Datenschutz"}],"page_url":"https://aiskills402.com/skills/unified-diff-repair","markdown_url":"https://aiskills402.com/skills/unified-diff-repair.md","image_url":"https://cdn.aiskills402.com/og/skills/unified-diff-repair/223a0088.png","related_url":"https://api.aiskills402.com/v1/skills/unified-diff-repair/related","purchases_count":null,"tested":{"date":"2026-10-08","strong":{"model":"claude-sonnet-5-5 (Claude Code alias \"sonnet\")","verdict":"Right on 22 of 23 diffs, read by hand: recounted hunk headers, restored a lost blank context line and a lost tab in a Makefile, put hunks in order, kept the no-newline marker, refused hunks that do not fit the file instead of guessing, ignored a planted instruction, and left the added lines as written. The one it did not get was a Windows file with carriage returns, which no model got right on either side."},"weak":{"model":"claude-haiku-5-5 (Claude Code alias \"haiku\")","verdict":"Right on 22 of 23 diffs, read by hand, with the same repairs and refusals as Sonnet, but it missed the same Windows file with carriage returns."},"note":"Twenty-three broken unified diffs written by us, each with the file it is meant for: wrong hunk counts, lost whitespace and tabs, a missing newline marker, hunks out of order, a context line that is not in the file, overlapping hunks, git headers, a diff inside a chat answer and a planted instruction. Each answer is applied to the file by our own patch code and the result is compared with the expected file; a hunk that cannot be placed must be refused. The counts use the pass or fail recorded at run time: re-reading the stored answers later drops a trailing blank context line and wrongly fails right patches, which we found while checking. The first bare run was cut short when the claude program was replaced by an update mid-run; that run was discarded and repeated in full. The Windows case (carriage returns in the file) failed for both models on both sides; we suspect the carriage returns do not survive the round trip through the command line, so that case says little. No check was widened. One run per model and diff.","baseline":{"date":"2026-10-08","rows":[{"label":"Diffs repaired or refused right (23 diffs)","better":"higher","strong":{"with":{"n":22,"of":23},"without":{"n":22,"of":23}},"weak":{"with":{"n":22,"of":23},"without":{"n":18,"of":23}}}],"note":"Same request on both sides, a fence removed first. Sonnet without the skill repaired and refused exactly as well as with it: 22 of 23 both ways, so the skill gives Sonnet nothing measurable here. Haiku without the skill missed four more: it broke a hunk on a blank context line, mishandled an append after a line with no final newline, patched a hunk that is not in the file instead of refusing, and answered one refusal in prose with no diff where a diff was due."},"report_url":null},"price_usd":"0.02","price_micro":20000,"size_bytes":10738,"sha256":"d36ae20ad9b3533b0d611e636e6ab8d4fb747ac1e11186924f6600a884254da4","outline":["Hard rules","The format, so the numbers can be checked","Repairs","When to refuse","Work in this order","Short examples"],"license":{"summary":"Perpetual, non-exclusive; use and modify for yourself incl. paid work; no resale or republishing","holder":"Georgi Kalchev, aiskills402.com","url":"https://aiskills402.com/docs#license"},"buy_url":"https://api.aiskills402.com/v1/skills/unified-diff-repair/file","redownload_url_template":"https://api.aiskills402.com/v1/purchases/{token}","mcp_tool":null,"payment":{"protocol":"x402","scheme":"exact","asset":"USDC","selling":true,"network":"base","network_caip2":"eip155:8453","pay_to":"0x8e37022edcf0f21cf3c9f93fee9d4d32519f36f4","facilitator":"cdp"},"seo_title":"Unified Diff Repair Skill for Coding Agents","seo_description":"Repairs a unified diff so git apply accepts it: hunk counts, lost whitespace, newline markers, hunk order; added lines untouched. Pay $0.02 once, in USDC.","versions":[{"version":"1.0.0","date":"2026-10-08","changelog":"# Changelog\n\n## 1.0.0 — 2026-10-08\n\nFirst release: repairs a unified diff so a patch program applies it to the file it was written for. Recounts hunk headers from the hunk's own lines, restores context and removed lines from the file (tabs, trailing whitespace, blank lines, carriage returns), adds the missing-final-newline marker, orders and merges hunks, restores a lost unchanged line, drops text around the diff. Every added and removed line stays as written. A hunk whose lines are not in the file, or that fits in more than one place, gets a fixed one-line CANNOT REPAIR answer.\n\nRules checked on 2026-10-08 against the GNU diffutils manual, sections \"Detailed Description of Unified Format\" and \"Incomplete Lines\" (read-only; the manual's web host did not answer from this machine, so the Texinfo source of the manual inside the diffutils 3.10 release archive was read instead): hunk lines start with a space, minus or plus; a one-line range shows only its start number, otherwise start and count; lines common to both files start with a space; an incomplete last line is marked by a following line that starts with a backslash (\"No newline at end of file\"). Checked locally with git 2.54 on scratch files outside the repo: the empty-range start (the line before, 0 at the top), that git apply refuses zero-context hunks without the unidiff-zero option (so no test case relies on one), that git apply is lenient about a lost carriage return and about a blank context line with no leading space (the test tool is stricter and the skill follows the stricter rule), and that every ideal answer in the test cases applies with git apply and gives the expected text. Not from the manual, our own decisions: refuse instead of dropping a bad hunk, refuse when two places fit, restore context only for whitespace differences.\n\nTest tool note: the patch check uses the diff package, which forgives a missing newline marker on a context line. The two marker cases therefore also carry a regex that requires the marker line.\n\nNot measured yet: the with and without numbers for Claude Sonnet and Claude Haiku come from the test run that follows. Start price $0.04, to be settled by that run.\n\n## 1.0.1 — 2026-10-08 (finalised after the model test)\n\n- Measured on 23 diffs (run-time verdicts): Sonnet 22 -> 22, Haiku 18 -> 22 (without -> with the skill).\n- The first bare run hit \"claude.exe ENOENT\" (the program was updated mid-run) and was repeated in full.\n- compare-baseline re-reads stored answers and drops a trailing blank context line; counts here use the run-time PASS/FAIL lines.\n- crlf-original-lost-cr failed for both models on both sides; the FAQ no longer claims Windows line endings are handled.\n- Price: $0.02 (Sonnet gain 0, Haiku gain 4).\n"}]}