biomejs/biome · error · anyhow::Error
invalid range: start offset ({start:?}) > end offset ({end:?
Error message
invalid range: start offset ({start:?}) > end offset ({end:?}) for {range:?} What it means
from_proto::text_range converts an incoming LSP Range to a Biome TextRange by converting both endpoints to byte offsets, then asserts start <= end via anyhow::ensure! (from_proto.rs:38-44). The error fires when the client sent a range whose start position is after its end position (by line, then character), which cannot form a valid TextRange.
Source
Thrown at crates/biome_lsp_converters/src/from_proto.rs:40
};
line_index.to_utf8(enc, line_col)
}
};
line_index
.offset(line_col)
.with_context(|| format!("position {position:?} is out of range"))
}
/// The function is used to convert a LSP range to TextRange.
pub fn text_range(
line_index: &LineIndex,
range: Range,
position_encoding: PositionEncoding,
) -> Result<TextRange> {
let start = offset(line_index, range.start, position_encoding)?;
let end = offset(line_index, range.end, position_encoding)?;
anyhow::ensure!(
start <= end,
"invalid range: start offset ({start:?}) > end offset ({end:?}) for {range:?}"
);
Ok(TextRange::new(start, end))
}
View on GitHub (pinned to 7529811358)
Solutions
- Normalize the range before sending: order the endpoints by (line, character) and swap them if inverted.
- When writing a client, pass positions straight from the editor API instead of reconstructing Range objects manually.
- On the server side, handle this conversion error and answer the request with an LSP error response rather than letting it propagate.
Example fix
// before: endpoints swapped (start after end)
const range = { start: { line: 10, character: 5 }, end: { line: 2, character: 0 } };
// after: normalize before sending
function posLe(a, b) {
return a.line < b.line || (a.line === b.line && a.character <= b.character);
}
const range = posLe(startPos, endPos)
? { start: startPos, end: endPos }
: { start: endPos, end: startPos }; Defensive patterns
Strategy: validation
Validate before calling
// Normalize every range before sending it to the server
function posLe(a, b) {
return a.line < b.line || (a.line === b.line && a.character <= b.character);
}
function normalizeRange(range) {
return posLe(range.start, range.end) ? range : { start: range.end, end: range.start };
}
const params = { textDocument, range: normalizeRange(selection) }; Type guard
function posLe(a, b) {
return a.line < b.line || (a.line === b.line && a.character <= b.character);
}
function isValidRange(range) {
return (
range !== null &&
typeof range === "object" &&
posLe(range.start, range.end)
);
} Try / catch
try {
const edits = await connection.sendRequest("textDocument/rangeFormatting", params);
} catch (err) {
if (String(err?.message ?? err).includes("invalid range: start offset")) {
return connection.sendRequest("textDocument/rangeFormatting", {
...params,
range: normalizeRange(params.range),
});
}
throw err;
} Prevention
- Always order range endpoints by (line, character) before constructing an LSP Range; swap inverted selections.
- Pass positions obtained directly from the editor API instead of rebuilding them by hand.
- Wrap range construction in one normalizeRange helper used everywhere, so a single place enforces ordering.
When it happens
Trigger: A client sends range.start greater than range.end - a backwards user selection the editor did not normalize, or client code constructing Range with the endpoints swapped. Hit by every server handler that converts client-provided ranges (formatting ranges, code action ranges, refactor selections).
Common situations: Custom LSP clients or scripts building Position/Range objects by hand; editors that report inverted selections; copy-pasted range literals with start/end reversed.
Related errors
- Failed to parse URI {}: {e}
- incoming message from the remote workspace is missing the Co
- could not find colon token in "${line}"
- invalid value for Content-Type expected "${MIME_JSONRPC}", g
- failed to deserialize incoming message from remote workspace
AI-assisted analysis of biomejs/biome@7529811358 (2026-08-16).
Data as JSON: /api/errors/0894562e6540ea25.
Report an issue: GitHub.