{"record":{"id":"a9572f951e2398f1","repo":"can1357/oh-my-pi","slug":"the-last-line-of-the-patch-must-be-end-patch","errorCode":null,"errorMessage":"The last line of the patch must be '*** End Patch'","messagePattern":"The last line of the patch must be '\\*\\*\\* End Patch'","errorType":"validation","errorClass":"ParseError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/edit/apply-patch/parser.ts","lineNumber":75,"sourceCode":"\tlet lines = patchText.trim().split(\"\\n\");\n\n\t// Lenient heredoc strip: <<EOF / <<'EOF' / <<\"EOF\" ... EOF\n\tif (lines.length >= 2) {\n\t\tconst first = lines[0];\n\t\tconst last = lines[lines.length - 1].trim();\n\t\tconst validOpeners = new Set([\"<<EOF\", \"<<'EOF'\", '<<\"EOF\"']);\n\t\tif (validOpeners.has(first) && last === \"EOF\") {\n\t\t\tlines = lines.slice(1, lines.length - 1);\n\t\t}\n\t}\n\n\tif (lines.length === 0 || lines[0].trim() !== BEGIN_PATCH_MARKER) {\n\t\tif (streaming) return [];\n\t\tthrow new ParseError(\"The first line of the patch must be '*** Begin Patch'\");\n\t}\n\tconst hasEndMarker = lines[lines.length - 1].trim() === END_PATCH_MARKER;\n\tif (!hasEndMarker && !streaming) {\n\t\tthrow new ParseError(\"The last line of the patch must be '*** End Patch'\");\n\t}\n\n\tconst hunks: PatchInput[] = [];\n\tlet remaining = hasEndMarker ? lines.slice(1, lines.length - 1) : lines.slice(1);\n\t// Line numbers are 1-based and include the `*** Begin Patch` line (= 1).\n\tlet lineNumber = 2;\n\n\twhile (remaining.length > 0) {\n\t\t// Blank separator lines between hunks are ignored (spec §3.3).\n\t\tif (remaining[0].trim() === \"\") {\n\t\t\tremaining = remaining.slice(1);\n\t\t\tlineNumber++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst firstLine = remaining[0].trim();\n\n\t\tif (firstLine.startsWith(ADD_FILE_MARKER)) {","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/edit/apply-patch/parser.ts#L57-L93","documentation":"The parser requires the literal '*** End Patch' marker as the final line of a complete patch. In non-streaming mode an unterminated patch is a ParseError; in streaming mode it is tolerated because more data may follow. This ensures the patch is complete before any file operations run.","triggerScenarios":"Calling parseApplyPatch/applyCodexPatch with patch text that starts with '*** Begin Patch' but is truncated or missing the closing '*** End Patch' line (streaming=false).","commonSituations":"Model output cut off by a token limit; patch copied only partially; a trailing newline/extra text after the marker causing the last line check to fail; log capture that dropped the final line.","solutions":["Append the exact line '*** End Patch' as the last line of the patch","Increase the model's max output tokens / re-generate if the patch was truncated","Trim trailing junk so the last line is exactly the marker","Use streaming parsing if you intentionally parse incomplete patches as they arrive"],"exampleFix":"// before\nawait applyCodexPatch('*** Begin Patch\\n*** Update File: a.ts\\n@@ ...'); // truncated\n// after\nawait applyCodexPatch(patchText.trimEnd() + '\\n*** End Patch');","handlingStrategy":"try-catch","validationCode":"if (patchText.trimStart().startsWith('*** Begin Patch') && !patchText.trimEnd().endsWith('*** End Patch')) {\n\tthrow new SkipOperation('patch truncated: missing End Patch marker');\n}","typeGuard":"function isCompletePatch(text) {\n\tconst t = text.trim();\n\treturn t.startsWith('*** Begin Patch') && t.endsWith('*** End Patch');\n}","tryCatchPattern":"try {\n\tawait applyCodexPatch(patchText);\n} catch (err) {\n\tif (err instanceof ParseError && err.message.includes(\"'*** End Patch'\")) {\n\t\tpatchText = patchText.trimEnd() + '\\n*** End Patch';\n\t\tawait applyCodexPatch(patchText);\n\t}\n}","preventionTips":["Check for the End marker before applying","Raise max output tokens to avoid truncation","Use streaming parse mode when consuming partial output"],"tags":["patch","parser","truncated-input","format-validation"],"backgroundTag":"malformed-patch","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}