can1357/oh-my-pi · error · ParseError

The last line of the patch must be '*** End Patch'

Error message

The last line of the patch must be '*** End Patch'

What it means

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.

Source

Thrown at packages/coding-agent/src/edit/apply-patch/parser.ts:75

	let lines = patchText.trim().split("\n");

	// Lenient heredoc strip: <<EOF / <<'EOF' / <<"EOF" ... EOF
	if (lines.length >= 2) {
		const first = lines[0];
		const last = lines[lines.length - 1].trim();
		const validOpeners = new Set(["<<EOF", "<<'EOF'", '<<"EOF"']);
		if (validOpeners.has(first) && last === "EOF") {
			lines = lines.slice(1, lines.length - 1);
		}
	}

	if (lines.length === 0 || lines[0].trim() !== BEGIN_PATCH_MARKER) {
		if (streaming) return [];
		throw new ParseError("The first line of the patch must be '*** Begin Patch'");
	}
	const hasEndMarker = lines[lines.length - 1].trim() === END_PATCH_MARKER;
	if (!hasEndMarker && !streaming) {
		throw new ParseError("The last line of the patch must be '*** End Patch'");
	}

	const hunks: PatchInput[] = [];
	let remaining = hasEndMarker ? lines.slice(1, lines.length - 1) : lines.slice(1);
	// Line numbers are 1-based and include the `*** Begin Patch` line (= 1).
	let lineNumber = 2;

	while (remaining.length > 0) {
		// Blank separator lines between hunks are ignored (spec §3.3).
		if (remaining[0].trim() === "") {
			remaining = remaining.slice(1);
			lineNumber++;
			continue;
		}

		const firstLine = remaining[0].trim();

		if (firstLine.startsWith(ADD_FILE_MARKER)) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Append the exact line '*** End Patch' as the last line of the patch
  2. Increase the model's max output tokens / re-generate if the patch was truncated
  3. Trim trailing junk so the last line is exactly the marker
  4. Use streaming parsing if you intentionally parse incomplete patches as they arrive

Example fix

// before
await applyCodexPatch('*** Begin Patch\n*** Update File: a.ts\n@@ ...'); // truncated
// after
await applyCodexPatch(patchText.trimEnd() + '\n*** End Patch');
Defensive patterns

Strategy: try-catch

Validate before calling

if (patchText.trimStart().startsWith('*** Begin Patch') && !patchText.trimEnd().endsWith('*** End Patch')) {
	throw new SkipOperation('patch truncated: missing End Patch marker');
}

Type guard

function isCompletePatch(text) {
	const t = text.trim();
	return t.startsWith('*** Begin Patch') && t.endsWith('*** End Patch');
}

Try / catch

try {
	await applyCodexPatch(patchText);
} catch (err) {
	if (err instanceof ParseError && err.message.includes("'*** End Patch'")) {
		patchText = patchText.trimEnd() + '\n*** End Patch';
		await applyCodexPatch(patchText);
	}
}

Prevention

When it happens

Trigger: Calling parseApplyPatch/applyCodexPatch with patch text that starts with '*** Begin Patch' but is truncated or missing the closing '*** End Patch' line (streaming=false).

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/a9572f951e2398f1. Report an issue: GitHub.