can1357/oh-my-pi · error · MCPTransportError
Notify timeout after ${timeout}ms
Error message
Notify timeout after ${timeout}ms What it means
The notification POST exceeded the configured MCP timeout. The fetch (or body read for piggybacked SSE data) was aborted by the timeout controller, detected via operation.isTimeoutAbort, and rethrown as a timeout-stage MCPTransportError that is explicitly non-retryable — notifications have no response, so a timeout cannot distinguish delivered from undelivered and a blind replay could double-apply side effects.
Source
Thrown at packages/coding-agent/src/mcp/transports/http.ts:820
if (contentType.includes("text/event-stream") && response.body) {
// Use the SSE connection's signal if available; otherwise keep the existing finite read timeout.
if (this.#sseConnection) {
this.#trackBackgroundDrain(
this.#readSSEStream(response.body, this.#operationSignal(this.#sseConnection.signal)),
);
} else {
const readOperation = createMCPTimeout(timeout, this.#operationSignal());
const signal = readOperation.signal ?? getNeverAbortSignal();
this.#trackBackgroundDrain(
this.#readSSEStream(response.body, signal).finally(() => readOperation.clear()),
);
}
} else {
await response.body?.cancel();
}
} catch (error) {
if (operation.isTimeoutAbort(error)) {
throw new MCPTransportError({
transport: "http",
stage,
failure: "timeout",
message: `Notify timeout after ${timeout}ms`,
retryable: false,
traceId,
cause: error,
});
}
if (error instanceof Error && error.name === "AbortError") throw error;
throw normalizeMCPTransportError(error, { transport: "http", stage, traceId });
} finally {
operation.clear();
}
}
close(): Promise<void> {
if (this.#closePromise) return this.#closePromise;View on GitHub (pinned to 9690622007)
Solutions
- Raise the MCP timeout configuration (config.timeout) if the server legitimately takes longer than the current limit.
- Check server load/latency and fix the slow handler blocking the notification path.
- Verify network path health (proxies, VPN) for stalls; the error includes a traceId to correlate with server logs.
- Do not auto-retry this error: confirm on the server whether the notification was applied before resending, to avoid duplicate effects.
- If notifications routinely time out, switch the server to return 202 immediately and process asynchronously.
Example fix
// before: default/aggressive timeout
const transport = new HttpTransport({ url, timeout: 5000 });
// after: headroom for slow notification acceptance
const transport = new HttpTransport({ url, timeout: 30000 }); Defensive patterns
Strategy: retry
Validate before calling
// size check before sending a bulky notification
const payload = JSON.stringify({ jsonrpc: '2.0', method, params });
if (payload.length > 1_000_000) console.warn('Notification payload large; raise timeout or split'); Type guard
function isNotifyTimeout(e: unknown): e is MCPTransportError {
return e instanceof MCPTransportError && e.failure === 'timeout' && /Notify timeout after/.test(e.message);
} Try / catch
try {
await transport.notify(method, params);
} catch (e) {
if (isNotifyTimeout(e)) {
// delivery unknown — never blind-retry; verify server-side state first
logger.warn('MCP notify timed out', { traceId: e.traceId });
} else throw e;
} Prevention
- Configure a timeout with realistic headroom for your server's p99 latency.
- Keep the server event loop unblocked so notification POSTs are acknowledged fast.
- Monitor network path (VPN/proxy) for stalls.
- Make notification handling idempotent server-side so manual replays are safe.
When it happens
Trigger: Server slow to accept the POST (overloaded, GC pause, blocked handler); configured timeout lower than server processing latency; network stall between client and server; response SSE piggyback stream kept open longer than the timeout.
Common situations: Large notification payloads over slow links; MCP server handling a long-running tool that blocks its event loop; timeout config tuned for requests applied to a heavily loaded server; proxy queuing delays.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Request timeout after ${timeout}ms
- HTTP ${response.status}: ${text}
- MCP request failed: ${response.status} ${response.statusText
- MCP OAuth refresh failed: ${response.status} ${text}
- HTTP ${response.status}: server redirected a ${init.method}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/598c732e06dda8b6.
Report an issue: GitHub.