can1357/oh-my-pi · error · ParseError

Expected hunk to start with @@ context marker, got: '${lines

Error message

Expected hunk to start with @@ context marker, got: '${lines[0]}'

What it means

Thrown when parseOneHunk is called with allowMissingContext=false and the first line of the candidate hunk does not start with '@@' at all. The parser expects every hunk to begin with an '@@' context/line-number marker (unified header, '@@ <context>' form, or empty '@@ @@'); anything else is rejected so content lines are never silently mis-attributed. Callers that allow context-less hunks pass allowMissingContext=true and skip this error.

Source

Thrown at packages/coding-agent/src/edit/diff.ts:611

			if (oldStartLine < 1) {
				throw new ParseError("Line hint must be >= 1", lineNumber);
			}
		} else if (TOP_OF_FILE_REGEX.test(normalizedContextValue)) {
			oldStartLine = 1;
			newStartLine = 1;
		} else if (trimmedContextValue.length > 0) {
			changeContexts.push(contextValue);
		}
		startIndex = 1;
	} else if (isHeaderLine) {
		const contextValue = headerTrimmed.slice(2).trim();
		if (contextValue.length > 0) {
			changeContexts.push(contextValue);
		}
		startIndex = 1;
	} else {
		if (!allowMissingContext) {
			throw new ParseError(`Expected hunk to start with @@ context marker, got: '${lines[0]}'`, lineNumber);
		}
		startIndex = 0;
	}

	if (oldStartLine !== undefined && oldStartLine < 1) {
		throw new ParseError(`Line numbers must be >= 1 (got ${oldStartLine})`, lineNumber);
	}
	if (newStartLine !== undefined && newStartLine < 1) {
		throw new ParseError(`Line numbers must be >= 1 (got ${newStartLine})`, lineNumber);
	}

	while (startIndex < lines.length) {
		const nextLine = lines[startIndex];
		if (!nextLine.startsWith("@@")) {
			break;
		}
		const trimmed = nextLine.trimEnd();
		if (trimmed.startsWith(CHANGE_CONTEXT_MARKER)) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Add the missing '@@' header line at the start of the hunk (e.g. '@@@ filename @@' or a unified '@@ -a,b +c,d @@').
  2. Check that the diff was split into hunks/files correctly before parsing each part.
  3. If header-less hunks are acceptable in your pipeline, use the parse path with allowMissingContext=true.
  4. Inspect the first line printed in the message — it shows exactly what the parser saw.

Example fix

// before
+console.log("hi")
// after
@@ -1 +1 @@
+console.log("hi")
Defensive patterns

Strategy: validation

Validate before calling

const first = diff.trimStart().split("\n")[0];
if (first !== undefined && !first.startsWith("@@")) {
  throw new Error(`Hunk must start with '@@' header, got: ${JSON.stringify(first.slice(0, 40))}`);
}

Try / catch

try {
  hunks = parseDiffHunks(diff);
} catch (err) {
  if (err instanceof ParseError && err.message.startsWith("Expected hunk to start with @@")) {
    // show the offending line; ask the generator/model to re-emit with a header
  } else throw err;
}

Prevention

When it happens

Trigger: Calling parseDiffHunks on a diff whose first hunk line is raw content ('+foo', ' context', or a file marker) instead of an '@@' header; passing a stray text fragment where a hunk is expected.

Common situations: Patches missing the header because an editor stripped lines starting with '@'; models emitting only +/- lines; splitting a multi-file patch incorrectly so a content line becomes a hunk start.

Related errors


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