{"record":{"id":"fe2727a535c0eff2","repo":"can1357/oh-my-pi","slug":"update-file-hunk-for-path-path-is-empty","errorCode":null,"errorMessage":"Update file hunk for path '${path}' is empty","messagePattern":"Update file hunk for path '(.+?)' is empty","errorType":"validation","errorClass":"ParseError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/edit/apply-patch/parser.ts","lineNumber":157,"sourceCode":"\t\t\t\tconst line = remaining[0];\n\t\t\t\tif (\n\t\t\t\t\tline.startsWith(\"*** Add File:\") ||\n\t\t\t\t\tline.startsWith(\"*** Delete File:\") ||\n\t\t\t\t\tline.startsWith(\"*** Update File:\")\n\t\t\t\t) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdiffLines.push(line);\n\t\t\t\tremaining = remaining.slice(1);\n\t\t\t\tlineNumber++;\n\t\t\t}\n\n\t\t\tif (diffLines.length === 0) {\n\t\t\t\tif (streaming) {\n\t\t\t\t\thunks.push({ path, op: \"update\", rename: movePath, diff: \"\" });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthrow new ParseError(`Update file hunk for path '${path}' is empty`, lineNumber);\n\t\t\t}\n\n\t\t\thunks.push({ path, op: \"update\", rename: movePath, diff: diffLines.join(\"\\n\") });\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (streaming) {\n\t\t\tbreak;\n\t\t}\n\t\tthrow new ParseError(\n\t\t\t`'${firstLine}' is not a valid hunk header. Valid hunk headers: '*** Add File: {path}', '*** Delete File: {path}', '*** Update File: {path}'`,\n\t\t\tlineNumber,\n\t\t);\n\t}\n\n\treturn hunks;\n}\n","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/edit/apply-patch/parser.ts#L139-L175","documentation":"A '*** Update File:' hunk must be followed by at least one diff/context line. When the parser reaches the next hunk header or end of patch without any diff lines for the update, it throws (non-streaming); streaming mode instead emits an empty-diff hunk to be resolved later.","triggerScenarios":"Parsing a patch containing '*** Update File: path' immediately followed by another hunk header or the End marker with no @@/context/+/- lines in between.","commonSituations":"Model emitted the Update File header but omitted the body (truncation or laziness); blank lines separating hunks got stripped so only whitespace remained; a rename-only intent ('*** Update File: a -> b') with no content changes.","solutions":["Provide at least one hunk (e.g. a @@ section with context lines) under every Update File header","If no changes are needed for that file, remove the Update File hunk entirely","Check for truncation if the patch came from an LLM and regenerate","For streaming consumers, handle the empty-diff hunk emitted instead of the throw"],"exampleFix":"// before\n*** Update File: src/a.ts\n*** End Patch\n// after\n*** Update File: src/a.ts\n@@\n-existing line\n+new line\n*** End Patch","handlingStrategy":"validation","validationCode":"const lines = patchText.split('\\n');\nfor (let i = 0; i < lines.length; i++) {\n\tif (lines[i].startsWith('*** Update File:') && !/^\\s*@@/.test(lines[i + 1] ?? '')) {\n\t\tthrow new SkipOperation(`Update File hunk at line ${i + 1} has no diff body`);\n\t}\n}","typeGuard":null,"tryCatchPattern":"try {\n\tawait applyCodexPatch(patchText);\n} catch (err) {\n\tif (err instanceof ParseError && err.message.includes('is empty')) {\n\t\t// regenerate the patch: a hunk body is missing\n\t}\n}","preventionTips":["Ensure every Update File header is followed by a @@ hunk","Drop Update File hunks with no changes","Detect LLM truncation and regenerate"],"tags":["patch","parser","format-validation","llm-output"],"backgroundTag":"malformed-patch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}