{"record":{"id":"539f672971b33760","repo":"charmbracelet/crush","slug":"invalid-start-line-d","errorCode":null,"errorMessage":"invalid start line: %d","messagePattern":"invalid start line: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/lsp/util/edit.go","lineNumber":95,"sourceCode":"\t// Only add a newline if the original file had one and we haven't already added it\n\tif endsWithNewline && !strings.HasSuffix(newContent.String(), lineEnding) {\n\t\tnewContent.WriteString(lineEnding)\n\t}\n\n\tif err := os.WriteFile(path, []byte(newContent.String()), 0o644); err != nil {\n\t\treturn fmt.Errorf(\"failed to write file: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc applyTextEdit(lines []string, edit protocol.TextEdit, encoding powernap.OffsetEncoding) ([]string, error) {\n\tstartLine := int(edit.Range.Start.Line)\n\tendLine := int(edit.Range.End.Line)\n\n\t// Validate positions before accessing lines.\n\tif startLine < 0 || startLine >= len(lines) {\n\t\treturn nil, fmt.Errorf(\"invalid start line: %d\", startLine)\n\t}\n\tif endLine < 0 || endLine >= len(lines) {\n\t\tendLine = len(lines) - 1\n\t}\n\n\tvar startChar, endChar int\n\tswitch encoding {\n\tcase powernap.UTF8:\n\t\t// UTF-8: Character offset is already a byte offset\n\t\tstartChar = int(edit.Range.Start.Character)\n\t\tendChar = int(edit.Range.End.Character)\n\tcase powernap.UTF16:\n\t\t// UTF-16 (default): Convert to byte offset\n\t\tstartLineContent := lines[startLine]\n\t\tendLineContent := lines[endLine]\n\t\tstartChar = powernap.PositionToByteOffset(startLineContent, edit.Range.Start.Character)\n\t\tendChar = powernap.PositionToByteOffset(endLineContent, edit.Range.End.Character)\n\tdefault:","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/lsp/util/edit.go#L77-L113","documentation":"applyTextEdit validates that the edit's start line falls within the split document lines before indexing. A start line that is negative or >= len(lines) is rejected with this message. This is a guard against stale or malformed LSP positions.","triggerScenarios":"An edit whose Range.Start.Line is out of bounds for the current document (empty file with line > 0, positions from a longer previous version, or 1-based vs 0-based line confusion).","commonSituations":"Edit computed against a document that has since shrunk or been emptied; manually constructed edits using 1-based line numbers; tool-generated edits after a bulk deletion.","solutions":["Regenerate the edit against the current document contents.","Use 0-based line numbers as the LSP protocol requires.","Clamp the start line to len(lines)-1 when applying legacy edits.","Validate the range against the document length before constructing the TextEdit."],"exampleFix":"// before\nstartLine := 1 // 1-based, document has 1 line\n// after\nstartLine := 0 // LSP uses 0-based lines","handlingStrategy":"validation","validationCode":"if int(edit.Range.Start.Line) < 0 || int(edit.Range.Start.Line) >= len(strings.Split(content, \"\\n\")) {\n    return fmt.Errorf(\"start line %d out of range\", edit.Range.Start.Line)\n}","typeGuard":null,"tryCatchPattern":"if strings.Contains(err.Error(), \"invalid start line\") {\n    // rebuild edits from current document snapshot and retry\n}","preventionTips":["Always use 0-based line numbers per LSP spec.","Recompute edits whenever the document version changes.","Clamp legacy positions to the document length before applying."],"tags":["lsp","validation","position-encoding"],"backgroundTag":"invalid-text-edit-range","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}