can1357/oh-my-pi · error · ToolError

LSP ${action} timed out after ${timeoutSec}s on ${serverName

Error message

LSP ${action} timed out after ${timeoutSec}s on ${serverName}. The server may still be indexing; try again or pass timeout=<larger>.

What it means

When an LSP tool action exceeds its wall-clock budget, the tool converts the generic abort into a descriptive ToolError naming the action, the budget in seconds, and the server, and suggests retrying with a larger timeout. The server itself may still be indexing and remains usable.

Source

Thrown at packages/coding-agent/src/lsp/tool.ts:1525

				default:
					output = `Unknown action: ${action}`;
			}

			return {
				content: [{ type: "text", text: output }],
				details: { serverName, action, success: true, request: params },
				...(useless ? { useless: true } : {}),
			};
		} catch (err) {
			if (err instanceof ToolError) throw err;
			if (err instanceof ToolAbortError || signal?.aborted) {
				// Distinguish a wall-clock timeout from a caller cancel:
				// callerSignal aborting → real cancel (re-throw ToolAbortError);
				// timeoutSignal aborting without callerSignal → emit a ToolError naming the
				// elapsed budget and server, instead of opaque "Operation aborted".
				if (timeoutSignal.aborted && !callerSignal?.aborted) {
					throw new ToolError(
						`LSP ${action} timed out after ${timeoutSec}s on ${serverName}. The server may still be indexing; try again or pass timeout=<larger>.`,
					);
				}
				throw new ToolAbortError();
			}
			const errorMessage = err instanceof Error ? err.message : String(err);
			return {
				content: [{ type: "text", text: `LSP error: ${errorMessage}` }],
				details: { serverName, action, success: false, request: params },
			};
		}
	}
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Retry the same call — indexing may have completed in the meantime.
  2. Pass a larger `timeout` value in the tool params (respecting tools.maxTimeout).
  3. Wait for the server to finish indexing (check the `status` action) before issuing expensive queries.
  4. Raise the `tools.maxTimeout` setting if the default clamp is too low for your workflow.

Example fix

// before
await lspTool.execute({ action: "symbols", query: "foo" });
// after
await lspTool.execute({ action: "symbols", query: "foo", timeout: 120 });
Defensive patterns

Strategy: retry

Validate before calling

const status = await lspTool.execute({ action: "status" });
if (isIndexing(status)) await waitForIndexing(status); // before expensive queries

Try / catch

try {
  return await lspTool.execute({ action, /* ... */ });
} catch (err) {
  if (err instanceof ToolError && /timed out after/.test(err.message)) {
    await Bun.sleep(2000);
    return lspTool.execute({ action, timeout: (params.timeout ?? 30) * 2, /* ... */ });
  } throw err;
}

Prevention

When it happens

Trigger: execute() awaited a request (symbols, references, definition, etc.) past `timeoutSec` (from the `timeout` param clamped by tools.maxTimeout), the timeoutSignal fired, and the caller signal was not aborted — the catch branch sees ToolAbortError with timeoutSignal.aborted && !callerSignal?.aborted.

Common situations: First query after opening a large project while the language server is still indexing; slow servers (jdtls, rust-analyzer on huge workspaces) with the default timeout; symbol/project-wide queries over big codebases.

Understand the failure class

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/16def35357d8322f. Report an issue: GitHub.