{"record":{"id":"b2993c5f03ad1c0e","repo":"gitbutlerapp/gitbutler","slug":"failed-to-parse-diff-header","errorCode":null,"errorMessage":"Failed to parse diff header","messagePattern":"Failed to parse diff header","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/ui/src/lib/utils/diffParsing.ts","lineNumber":168,"sourceCode":"\treadonly newLines: number;\n\treadonly comment?: string;\n\treadonly contentSections: ContentSection[];\n};\n\ntype BaseDiffRows = { prevRows: BaseRow[]; nextRows: BaseRow[] };\n\nconst headerRegex =\n\t/@@ -(?<beforeStart>\\d+),?(?<beforeCount>\\d+)? \\+(?<afterStart>\\d+),?(?<afterCount>\\d+)? @@(?<comment>.+)?/;\nfunction parseHeader(header: string): {\n\toldStart: number;\n\tnewStart: number;\n\toldLines: number;\n\tnewLines: number;\n\tcomment?: string;\n} {\n\tconst result = headerRegex.exec(header);\n\tif (!result?.groups) {\n\t\tthrow new Error(\"Failed to parse diff header\");\n\t}\n\treturn {\n\t\toldStart: parseInt(result.groups[\"beforeStart\"]),\n\t\toldLines: parseInt(result.groups[\"beforeCount\"] ?? \"1\"),\n\t\tnewStart: parseInt(result.groups[\"afterStart\"]),\n\t\tnewLines: parseInt(result.groups[\"afterCount\"] ?? \"1\"),\n\t\tcomment: result.groups[\"comment\"],\n\t};\n}\n\nfunction lineType(line: string): SectionType {\n\tif (line.startsWith(\"+\")) {\n\t\treturn SectionType.AddedLines;\n\t} else if (line.startsWith(\"-\")) {\n\t\treturn SectionType.RemovedLines;\n\t} else {\n\t\treturn SectionType.Context;\n\t}","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/packages/ui/src/lib/utils/diffParsing.ts#L150-L186","documentation":"The agent-cleanup rewrite writes files atomically: read content, write a temp file in the same directory, then rename over the target. Right before persisting, it re-reads the target and compares to the snapshot it based the rewrite on; if they differ (someone wrote in between), it bails instead of silently discarding that edit. A later sweep re-runs on the fresh content, so backing off is safe.","triggerScenarios":"The managed file (e.g. an AGENTS.md/CLAUDE.md-style file with managed blocks) is modified between the initial read and the rename - another agent, editor autosave, formatter, or concurrent but command writing the same file while the cleanup sweep is in flight.","commonSituations":"Multiple agents/tools editing instruction files concurrently; IDE autosave or a linter rewriting the file mid-sweep; slow disks enlarging the read-to-rename window.","solutions":["Retry the sweep: re-running the cleanup command re-reads the fresh content and reapplies the rewrite","Avoid running two rewriting commands over the same file simultaneously; serialize cleanups in your tooling","If it persists, check for a formatter/watcher that keeps touching the file and pause it during the sweep"],"exampleFix":"# shell\n# before: 'file changed while preparing the rewrite; leaving it alone'\n but agent cleanup  # simply re-run; the sweep is idempotent over fresh content\n# after: rewrite applies cleanly","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match apply_rewrite(path, new_content) {\n    Err(err) if err.to_string().contains(\"changed while preparing\") => {\n        // benign back-off: re-read the file and re-run the sweep on fresh content\n    }\n    other => other,\n}","preventionTips":["Re-run the cleanup command; the sweep is idempotent over current content","Serialize file-rewriting commands (formatters, other agents) instead of racing them"],"tags":["rust","gitbutler","cli","race-condition","atomic-write","file-rewrite"],"backgroundTag":"concurrent-file-modification","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-23T16:17:53.355Z"}