{"record":{"id":"8ab89fd56f4f8705","repo":"can1357/oh-my-pi","slug":"line-numbers-must-be-1-got-oldstartline","errorCode":null,"errorMessage":"Line numbers must be >= 1 (got ${oldStartLine})","messagePattern":"Line numbers must be >= 1 \\(got (.+?)\\)","errorType":"validation","errorClass":"ParseError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/edit/diff.ts","lineNumber":617,"sourceCode":"\t\t} else if (trimmedContextValue.length > 0) {\n\t\t\tchangeContexts.push(contextValue);\n\t\t}\n\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) {","sourceCodeStart":599,"sourceCodeEnd":635,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/edit/diff.ts#L599-L635","documentation":"Thrown after header parsing when the resolved old start line (from a unified header or a line hint) is defined but less than 1. This is a final safety net covering paths that set oldStartLine outside the unified-header branch (e.g. context line hints), enforcing 1-based line numbers before any hunk content is consumed.","triggerScenarios":"parseOneHunk with a header that yields oldStartLine = 0 or negative — typically an '@@' context header with a 'line 0' hint that somehow bypassed the hint-specific check, or programmatic construction of the header object.","commonSituations":"Generated patches using 0-based line numbers; tooling that writes '@@ line 0' meaning top-of-file; round-tripping diffs through custom formatters that renumber lines.","solutions":["Renumber the old-side start line to >= 1 in the '@@' header.","Use the top-of-file marker instead of a line-0 hint.","Fix the upstream generator's index base (add 1).","Pre-validate parsed header numbers before handing the diff to the parser."],"exampleFix":"// before\n@@ util.ts line 0\n// after\n@@ util.ts line 1","handlingStrategy":"validation","validationCode":"// after extracting oldStartLine from any header form\nif (oldStartLine !== undefined && oldStartLine < 1) {\n  throw new Error(`old start line must be >= 1, got ${oldStartLine}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  hunks = parseDiffHunks(diff);\n} catch (err) {\n  if (err instanceof ParseError && err.message.startsWith(\"Line numbers must be >= 1\")) {\n    // renumber and retry\n  } else throw err;\n}","preventionTips":["Standardize on 1-based line numbers across all header forms (unified and context hints).","Reject 'line 0' at patch-generation time, not parse time.","Round-trip test: generate a patch, parse it, confirm no ParseError."],"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"}