Hmbown/CodeWhale · warning
LSP diagnostics timed out waiting for another document
Error message
LSP diagnostics timed out waiting for another document
What it means
`diagnostics_for` serializes open/version/send/wait transactions behind a mutex because one receiver cannot serve concurrent polling. If the gate cannot be acquired within `wait`, this error fires instead of risking interleaved reads on the diagnostics channel.
Solutions
- Serialize diagnostics requests in the caller instead of issuing them concurrently
- Increase the wait budget for the gate acquisition
- Add per-request timeout so a stuck transaction cannot starve the gate
- Use separate transports/channels per document if concurrency is required
Example fix
// before
let handles = files.iter().map(|f| tokio::spawn(diagnostics_for(f)));
// after
for f in &files {
diagnostics_for(f).await?; // one transaction at a time
} Defensive patterns
Strategy: try-catch
Validate before calling
// track in-flight diagnostics per transport before calling if (diagnosticsInFlight.has(transportId)) await diagnosticsInFlight.get(transportId);
Try / catch
match diagnostics_for(path, text, wait).await {
Err(e) if e.to_string().contains("waiting for another document") => {
eprintln!("diagnostics busy; retry after current document completes");
}
other => other,
} Prevention
- Issue diagnostics requests sequentially per transport
- Use a generous wait for the gate, distinct from the server-wait budget
- Ensure every gated section releases the lock on error paths
When it happens
Trigger: A second diagnostics request arrives while another document's wait transaction still holds `diagnostics_gate` and the first one does not release it within `wait`.
Common situations: UI polling diagnostics for many open files concurrently with a short wait budget; a previous diagnostics wait stuck on a hung server.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- child wall-time budget exhausted
- Clock lock failed
- for
- LSP diagnostics timed out before a current publication
- LSP diagnostics timed out sending document
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/d87a80ed87e0c4fc.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/lsp/client.rs:387
impl LspTransport for StdioLspTransport {
fn is_alive(&self) -> bool {
// stderr may close independently. The writer, reader and dispatcher
// are the protocol lifetime; none may have exited or been aborted.
!self.tx_outbound.is_closed() && self.tasks.iter().skip(1).all(|task| !task.is_finished())
}
async fn diagnostics_for(
&self,
path: &Path,
text: &str,
wait: Duration,
) -> Result<DiagnosticPublication> {
// One receiver cannot serve concurrent polling safely: serialize the
// open/version/send/wait transaction, including semantic ensure_open.
let deadline = tokio::time::Instant::now() + wait;
let _gate = timeout(wait, self.diagnostics_gate.lock())
.await
.map_err(|_| anyhow!("LSP diagnostics timed out waiting for another document"))?;
let path_buf = path.to_path_buf();
let (_, version) = timeout(
deadline.saturating_duration_since(tokio::time::Instant::now()),
self.open_or_change(path, text),
)
.await
.map_err(|_| anyhow!("LSP diagnostics timed out sending document"))??;
loop {
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
return Err(anyhow!(
"LSP diagnostics timed out before a current publication"
));
}
let mut rx = self.diagnostics_rx.lock().await;
let (file, published_version, items) = match timeout(remaining, rx.recv()).await {
Ok(Some(item)) => item,
Ok(None) => {View on GitHub (pinned to 73e0f67d83)