{"record":{"id":"9ec60c38d7f2aa98","repo":"gitbutlerapp/gitbutler","slug":"failed-to-parse-patch","errorCode":null,"errorMessage":"Failed to parse patch","messagePattern":"Failed to parse patch","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/lite/ui/src/routes/project/$id/workspace/diff-view.ts","lineNumber":118,"sourceCode":"\tselection: DiffLineSelection | null;\n\tfileByItemId: DiffView[\"fileByItemId\"];\n\tdiffStyle: DiffStyle;\n}): CodeViewLineSelection | null => {\n\tif (!selection) return null;\n\n\tconst file = fileByItemId.get(weakFileIdentityKey(selection.file));\n\tif (!file) return null;\n\n\tconst range = selection.range ?? file.hunks[0]?.ranges[diffStyle];\n\treturn range ? { id: file.item.id, range } : null;\n};\n\nconst parseFileDiff = (\n\tpatch: string,\n\tversion: string,\n): CodeViewDiffItem<Annotation>[\"fileDiff\"] => {\n\tconst parsed = processFile(patch, { cacheKey: version });\n\tif (!parsed) throw new Error(\"Failed to parse patch\");\n\n\treturn parsed;\n};\n\n/**\n * Prepare stable file IDs and patch versions once for both review state and\n * the rendered diff. Parsing remains separate so single-file mode only builds\n * Pierre's diff shape for the selected file.\n */\nexport const prepareDiffFiles = ({\n\tfileParent,\n\tchanges,\n\ttreeChangeDiffs,\n}: PrepareDiffFilesDeps): Array<PreparedDiffFile> =>\n\ttreeChangeDiffs.flatMap((treeChangeDiff, index) => {\n\t\tconst change = changes[index];\n\t\tif (change === undefined || treeChangeDiff === undefined) return [];\n\t\tconst file: FileAddress = { parent: fileParent, path: change.path };","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/apps/lite/ui/src/routes/project/$id/workspace/diff-view.ts#L100-L136","documentation":"Thrown by parseFileDiff when processFile (the diff-to-code-view parser) returns a falsy result for the given patch string. A non-empty patch that the parser cannot turn into a file diff structure indicates malformed, truncated, or unsupported diff content. The result is cached by version so re-parses with the same patch text are avoided.","triggerScenarios":"Calling parseFileDiff (via parsePreparedDiffFile) with a patch string that processFile cannot parse — returns null/undefined.","commonSituations":"Truncated diff payloads from the backend, empty or whitespace-only patches slipping through preparation, binary-file diffs rendered as text, or an unsupported diff format produced by a git version change.","solutions":["Log/inspect the failing patch string to see why processFile rejects it (empty, truncated, or binary).","Filter out empty or binary patches before calling parsePreparedDiffFile.","Ensure the backend produces well-formed unified diffs; check for recent changes to the diff producer.","Wrap parsePreparedDiffFile in try/catch and render a fallback 'diff unavailable' view per file."],"exampleFix":"// before\nconst fileDiff = parsePreparedDiffFile(prepared);\n// after\nlet fileDiff;\ntry {\n  fileDiff = parsePreparedDiffFile(prepared);\n} catch {\n  fileDiff = null; // render fallback in the diff view\n}","handlingStrategy":"try-catch","validationCode":"if (!patch || patch.trim() === '') return null; // skip empty patches before parsing","typeGuard":null,"tryCatchPattern":"try {\n  const fileDiff = parsePreparedDiffFile(prepared);\n} catch {\n  renderDiffUnavailableFallback();\n}","preventionTips":["Filter empty and binary patches before parsing","Verify backend diff payloads are complete unified diffs","Always render a per-file fallback so one bad patch doesn't break the view"],"tags":["diff","parsing","fallback"],"backgroundTag":"json-parse-error","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}