can1357/oh-my-pi · error · ParseError
Unexpected line in hunk: '${line}'. Lines must start with '
Error message
Unexpected line in hunk: '${line}'. Lines must start with ' ' (context), '+' (add), or '-' (remove) What it means
Thrown when the first content line of a hunk starts with '@@' yet is not a recognized nested context marker (and parsedLines is still 0), or generally when a line inside the hunk body does not start with ' ', '+', or '-'. Every hunk body line must be context (space prefix), an addition ('+'), or a removal ('-'); anything else is structurally invalid so the parser refuses it with the offending line quoted.
Source
Thrown at packages/coding-agent/src/edit/diff.ts:704
if (firstChar === undefined || firstChar === "") {
hunk.hasContextLines = true;
hunk.oldLines.push("");
hunk.newLines.push("");
} else if (firstChar === " ") {
hunk.hasContextLines = true;
hunk.oldLines.push(line.slice(1));
hunk.newLines.push(line.slice(1));
} else if (firstChar === "+") {
hunk.newLines.push(line.slice(1));
} else if (firstChar === "-") {
hunk.oldLines.push(line.slice(1));
} else if (!line.startsWith("@@")) {
hunk.hasContextLines = true;
hunk.oldLines.push(line);
hunk.newLines.push(line);
} else {
if (parsedLines === 0) {
throw new ParseError(
`Unexpected line in hunk: '${line}'. Lines must start with ' ' (context), '+' (add), or '-' (remove)`,
lineNumber + 1,
);
}
break;
}
parsedLines++;
}
if (parsedLines === 0) {
throw new ParseError("Hunk does not contain any lines", lineNumber + startIndex);
}
stripLineNumberPrefixes(hunk);
return { hunk, linesConsumed: parsedLines + startIndex };
}
function stripLineNumberPrefixes(hunk: DiffHunk): void {View on GitHub (pinned to 9690622007)
Solutions
- Prefix every body line correctly: keep leading space for context, '+' for additions, '-' for removals.
- Remove stray text/second '@@' headers from the hunk body.
- Re-copy the patch in a way that preserves leading whitespace (disable 'quote prefixing' and reflow).
- Pre-scan body lines with /^( |\+|-|\\|$)/ and reject/reject-fix before parsing.
Example fix
// before @@ -1,2 +1,2 @@ this line lost its prefix +added // after @@ -1,2 +1,2 @@ this line lost its prefix +added
Defensive patterns
Strategy: validation
Validate before calling
const body = diff.split("\n").filter(l => !l.startsWith("@@"));
const bad = body.find(l => l !== "" && !/^[ +-\\]/.test(l));
if (bad !== undefined) {
throw new Error(`Diff body line lacks ' '/'+'/'-' prefix: ${JSON.stringify(bad.slice(0, 40))}`);
} Try / catch
try {
hunks = parseDiffHunks(diff);
} catch (err) {
if (err instanceof ParseError && err.message.startsWith("Unexpected line in hunk")) {
// the message quotes the bad line; fix its prefix or remove it, then retry
} else throw err;
} Prevention
- Copy diffs in plain-text mode so leading spaces and +/- survive.
- Strip email quote prefixes ('>') and markdown artifacts before parsing.
- Ask LLM generators to emit raw unified-diff bodies with no prose interleaved.
When it happens
Trigger: parseDiffHunks on a hunk whose body contains raw text (no prefix), markdown, a second '@@ ...' unified header immediately after the first with no content between, or lines with a corrupted first character (e.g. tab or '>' instead of space).
Common situations: LLM-generated patches mixing prose with diff lines; emails/forum copy where diff prefixes were reflowed or quote-prefixed ('> +code'); tools that emit two headers back-to-back; whitespace-mangling formatters.
Related errors
- Expected hunk to start with @@ context marker, got: '${lines
- Line numbers in @@ header must be >= 1
- Line hint must be >= 1
- Line numbers must be >= 1 (got ${oldStartLine})
- Line numbers must be >= 1 (got ${newStartLine})
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/10cce8a238069bf3.
Report an issue: GitHub.