{"record":{"id":"9e49b1ff1702ce75","repo":"can1357/oh-my-pi","slug":"line-numbers-must-be-1-got-newstartline","errorCode":null,"errorMessage":"Line numbers must be >= 1 (got ${newStartLine})","messagePattern":"Line numbers must be >= 1 \\(got (.+?)\\)","errorType":"validation","errorClass":"ParseError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/edit/diff.ts","lineNumber":620,"sourceCode":"\t\tstartIndex = 1;\n\t} else if (isHeaderLine) {\n\t\tconst contextValue = headerTrimmed.slice(2).trim();\n\t\tif (contextValue.length > 0) {\n\t\t\tchangeContexts.push(contextValue);\n\t\t}\n\t\tstartIndex = 1;\n\t} else {\n\t\tif (!allowMissingContext) {\n\t\t\tthrow new ParseError(`Expected hunk to start with @@ context marker, got: '${lines[0]}'`, lineNumber);\n\t\t}\n\t\tstartIndex = 0;\n\t}\n\n\tif (oldStartLine !== undefined && oldStartLine < 1) {\n\t\tthrow new ParseError(`Line numbers must be >= 1 (got ${oldStartLine})`, lineNumber);\n\t}\n\tif (newStartLine !== undefined && newStartLine < 1) {\n\t\tthrow new ParseError(`Line numbers must be >= 1 (got ${newStartLine})`, lineNumber);\n\t}\n\n\twhile (startIndex < lines.length) {\n\t\tconst nextLine = lines[startIndex];\n\t\tif (!nextLine.startsWith(\"@@\")) {\n\t\t\tbreak;\n\t\t}\n\t\tconst trimmed = nextLine.trimEnd();\n\t\tif (trimmed.startsWith(CHANGE_CONTEXT_MARKER)) {\n\t\t\tconst nestedContext = trimmed.slice(CHANGE_CONTEXT_MARKER.length);\n\t\t\tif (nestedContext.trim().length > 0) {\n\t\t\t\tchangeContexts.push(nestedContext);\n\t\t\t}\n\t\t\tstartIndex++;\n\t\t} else if (trimmed === EMPTY_CHANGE_CONTEXT_MARKER) {\n\t\t\tstartIndex++;\n\t\t} else {\n\t\t\tbreak;","sourceCodeStart":602,"sourceCodeEnd":638,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/edit/diff.ts#L602-L638","documentation":"Same enforcement as the old-side check, but for the new start line: after header parsing, if newStartLine is defined and less than 1 the parser throws. It guarantees both sides of the hunk anchor at a valid 1-based position before content lines are consumed.","triggerScenarios":"parseOneHunk with a unified header like '@@ -1,5 +0,0 @@' or any header whose new-side start is 0/negative.","commonSituations":"Git-style file-deletion or file-creation hunks that use '+0,0' / '-0,0' conventions, which this parser rejects; hand-written diffs counting from zero; custom diff generators emitting 0-based new-side numbers.","solutions":["Renumber the new-side start line to >= 1 (e.g. '+1,0' instead of '+0,0').","For pure deletions, anchor the new side at line 1 with count 0.","Regenerate the diff with standard tooling and re-check the header.","Pre-validate both start numbers with a regex + Number check before parsing."],"exampleFix":"// before\n@@ -1,5 +0,0 @@\n-old line\n// after\n@@ -1,5 +1,0 @@\n-old line","handlingStrategy":"validation","validationCode":"const m = header.match(/^@@ -\\d+(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/);\nif (m && Number(m[1]) < 1) {\n  throw new Error(`new start line must be >= 1, got ${m[1]}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  hunks = parseDiffHunks(diff);\n} catch (err) {\n  if (err instanceof ParseError && err.message.includes(\"(got \")) {\n    // patch the header to a 1-based anchor and retry\n  } else throw err;\n}","preventionTips":["Never emit git-style '+0,0' conventions; anchor new side at line 1 with count 0 for deletions.","Regenerate patches with standard diff tooling rather than hand-writing headers.","Validate both sides of the header numerically before parsing."],"tags":["diff","parsing","validation"],"backgroundTag":"invalid-hunk-header","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}