can1357/oh-my-pi · error · ParseError

The first line of the patch must be '*** Begin Patch'

Error message

The first line of the patch must be '*** Begin Patch'

What it means

The apply-patch parser requires the literal marker '*** Begin Patch' as the first non-dropped line of the patch. In non-streaming mode a missing marker is a hard ParseError; in streaming mode it returns [] (waiting for more data). This guards against feeding arbitrary text into the patch format.

Source

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

}

function parseApplyPatchWithOptions(patchText: string, options: ParseApplyPatchOptions): PatchInput[] {
	const streaming = options.streaming === true;
	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;
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Ensure the patch text begins with the exact line '*** Begin Patch' (no fences, no leading prose)
  2. Strip markdown code fences and any preamble before the marker before parsing
  3. If the input might be a unified diff, route it to the unified-diff applier instead
  4. Catch ParseError and, for model-generated patches, re-request the patch in the correct format

Example fix

// before
await applyCodexPatch('```\n*** Begin Patch\n...\n```');
// after
const stripped = patchText.replace(/^```[a-z]*\n/, '').replace(/\n```$/, '');
await applyCodexPatch(stripped.trimStart());
Defensive patterns

Strategy: try-catch

Validate before calling

if (!patchText.trimStart().startsWith('*** Begin Patch')) {
	throw new SkipOperation('not a codex patch');
}

Type guard

function isCodexPatch(text) { return text.trimStart().startsWith('*** Begin Patch'); }

Try / catch

try {
	await applyCodexPatch(patchText);
} catch (err) {
	if (err instanceof ParseError && err.message.includes("'*** Begin Patch'")) {
		patchText = stripFencesAndPreamble(patchText);
		await applyCodexPatch(patchText);
	}
}

Prevention

When it happens

Trigger: Calling parseApplyPatch (via applyCodexPatch) with text whose first line is not exactly '*** Begin Patch' (after trim) — e.g. a model prefixed the patch with prose or a code fence, or used lowercase/variant markers.

Common situations: LLM output wrapped the patch in ``` fences so the first line is '```'; leading explanation text before the marker; copied patch lost its first line; applying a unified-diff (--- a/...) instead of a codex patch.

Related errors


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