denoland/deno · warning
Failed to convert range to tsc text span: {:#}
Error message
Failed to convert range to tsc text span: {:#} What it means
Thrown inside the Deno language server when an inlay-hints request's LSP range (UTF-16 line/character positions) cannot be converted to a tsc TextSpan (character offsets). TextSpan::from_range (cli/lsp/tsc.rs:1943) maps both endpoints through LineIndex::offset_tsc, which fails when a position is out of bounds for the document's current line index. It surfaces as a failed textDocument/inlayHint request in editor/LSP logs, not as a CLI error.
Source
Thrown at cli/lsp/ts_server.rs:1211
&snapshot,
token,
)
}
}
}
pub async fn provide_inlay_hint(
&self,
module: &DocumentModule,
range: lsp::Range,
snapshot: &Arc<StateSnapshot>,
token: &CancellationToken,
) -> Result<Option<Vec<lsp::InlayHint>>, AnyError> {
match self {
Self::Js(ts_server) => {
let text_span = tsc::TextSpan::from_range(range, &module.line_index)
.map_err(|err| {
anyhow!("Failed to convert range to tsc text span: {:#}", err)
})?;
let mut inlay_hints = ts_server
.provide_inlay_hints(snapshot.clone(), module, text_span, token)
.await;
// Silence tsc debug failures.
// See https://github.com/denoland/deno/issues/30455.
// TODO(nayeemrmn): Keeps tabs on whether this is still necessary.
if let Err(err) = &inlay_hints
&& err.to_string().contains("Debug Failure")
{
lsp_warn!("Unable to get inlay hints from TypeScript: {:#}", err);
inlay_hints = Ok(None)
}
inlay_hints?
.map(|inlay_hints| {
inlay_hints
.into_iter()
.map(|inlay_hint| {View on GitHub (pinned to 89f33cbef2)
Solutions
- Reload the file or restart the language server (VS Code: 'Deno: Restart Language Server' / Reload Window) to resynchronize snapshots
- Update Deno - stale-snapshot range conversions have received fixes in past releases
- If you maintain the editor extension, clamp the requested range to the document's real line/character bounds before sending the request
- If reproducible on current Deno, capture LSP logs (deno lsp with DENO_LOG=debug or the editor's Deno log panel) and report to denoland/deno
Example fix
// before (editor extension sends client-cached range)
const hints = await client.sendRequest('textDocument/inlayHint', {
textDocument: doc.uri,
range: cachedRange,
});
// after (clamp to the document the server has)
const lastLine = doc.lineCount - 1;
const endLine = Math.min(cachedRange.end.line, lastLine);
const range = {
start: { line: 0, character: 0 },
end: {
line: endLine,
character: Math.min(cachedRange.end.character, doc.lineAt(endLine).text.length),
},
};
const hints = await client.sendRequest('textDocument/inlayHint', {
textDocument: doc.uri,
range,
}); Defensive patterns
Strategy: validation
Validate before calling
// Editor/LSP client: clamp the range to the document before requesting inlay hints
function clampRange(doc, range) {
const lastLine = doc.lineCount - 1;
const endLine = Math.min(Math.max(range.end.line, 0), lastLine);
const endChar = Math.min(range.end.character, doc.lineAt(endLine).text.length);
const startLine = Math.min(Math.max(range.start.line, 0), endLine);
const startChar = startLine === endLine
? Math.min(range.start.character, endChar)
: Math.min(range.start.character, doc.lineAt(startLine).text.length);
return { start: { line: startLine, character: startChar }, end: { line: endLine, character: endChar } };
} Type guard
function isValidLspRange(doc, range) {
return (
Number.isInteger(range.start.line) && range.start.line < doc.lineCount &&
Number.isInteger(range.end.line) && range.end.line < doc.lineCount &&
range.start.character <= doc.lineAt(range.start.line).text.length &&
range.end.character <= doc.lineAt(range.end.line).text.length &&
(range.start.line < range.end.line ||
(range.start.line === range.end.line && range.start.character <= range.end.character))
);
} Try / catch
try {
const hints = await client.sendRequest('textDocument/inlayHint', params);
} catch (err) {
// One stale-range failure is benign: drop the hints for this version,
// do not retry with the same range - wait for the next change event.
if (/text span|inlay/i.test(String(err))) return null;
throw err;
} Prevention
- Always recompute the range from the current document version at request time instead of caching ranges
- Send didChange/didOpen before feature requests so the server snapshot matches the client's
- Version-stamp requests and discard responses whose version is older than the current document
When it happens
Trigger: An editor sends textDocument/inlayHint with a range whose start/end line or character exceeds the document content the server currently holds: range computed against an older document version (rapid edits, formatter run racing the request), a whole-document range on a file that just shrank, or a client extension computing positions past end-of-file.
Common situations: Fast typing or format-on-save invalidating a pending inlay-hints request; editor extensions (VS Code, Neovim/lspconfig, Zed) that cache ranges client-side; notebook cells where position mapping differs between client and server; stale snapshot after rapid open/close of files.
Related errors
- failed to send request to tsc thread
- Invalid cron schedule: start=${start}, end=${end}, every=${e
- Invalid type for fetch: must be a function
- Scope '{}' was not a directory path.
- JSR package info not found: {}
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/05dc1e30d5038d95.
Report an issue: GitHub.