ruvnet/ruflo · error
MCP server " " is in cooldown (HTTP , s remaining)
Error message
MCP server "${server.name}" is in cooldown (HTTP ${cd.status ?? "n/a"}, ${Math.round(remaining / 1000)}s remaining): ${cd.message} What it means
Same cooldown mechanism as the 429 case, but the recorded failure was a non-429 definitive HTTP status (401/403/404/500…) recorded by recordFailure. checkCooldown (clientPool.ts:111) throws this summary error for ~5s (DEFAULT_RATE_LIMIT_COOLDOWN_MS) after such a failure, including the original status and message, so rapid retries get a fast, clear signal instead of re-hitting the upstream.
Solutions
- Read the embedded original message/status — fix the root cause (url path, auth headers) in MCP_SERVERS config
- Wait out the ~5s cooldown; it clears automatically, or call evictFromPool(server) to clear it immediately after fixing config
- Verify the endpoint manually: curl -v with the same URL and headers
- For 5xx, check upstream health before retrying
Example fix
// before
const client = await getClient(server); // in cooldown after earlier 401
// after
import { evictFromPool, getClient } from "$lib/server/mcp/clientPool";
// after fixing the MCP_SERVERS url/headers:
evictFromPool(server); // drop stale cooldown for the old (broken) config
const client = await getClient(server); Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
function isCooldownError(e: unknown): boolean {
return e instanceof Error && /is in cooldown \(HTTP/i.test(e.message);
} Try / catch
try {
client = await getClient(server);
} catch (e) {
if (isCooldownError(e)) {
// wait out the ~5s window or evict after fixing root cause
await new Promise((r) => setTimeout(r, 5_000));
return getClient(server);
}
throw e;
} Prevention
- Fix the underlying HTTP failure (auth/URL) — the cooldown only mirrors it
- Use evictFromPool(server) after a config fix to skip the stale window
- Log the embedded original status/message so the root cause is not lost behind the cooldown wrapper
When it happens
Trigger: An earlier connect to the MCP server failed definitively (bad auth 401, wrong path 404, upstream 5xx); getClient() is called again for the same url+headers within the 5s cooldown window and gets this error with the remaining seconds.
Common situations: Wrong MCP_SERVERS url (missing /mcp path) or expired token causing 401/404, followed by quick retries from the UI or a startup loop; upstream 502 blip plus an aggressive retry.
Related errors
- Circuit breaker is open. Service temporarily unavailable.
- Circuit breaker open for provider
- Circuit breaker is open
- at least one candidate is required
- Can only fail running or assigned tasks
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/617bc4dd25526112.
Report an issue: GitHub.
Appendix: source
Thrown at ruflo/src/ruvocal/src/lib/server/mcp/clientPool.ts:111
if (Number.isFinite(seconds) && seconds > 0) {
return Math.min(seconds * 1000, MAX_RATE_LIMIT_COOLDOWN_MS);
}
}
return DEFAULT_RATE_LIMIT_COOLDOWN_MS;
}
function checkCooldown(key: string, server: McpServerConfig): void {
const cd = failureCooldown.get(key);
if (!cd) return;
const remaining = cd.until - Date.now();
if (remaining <= 0) {
failureCooldown.delete(key);
return;
}
if (cd.status === 429) {
throw new McpRateLimitedError(server.name, cd.status, remaining, cd.message);
}
throw new Error(
`MCP server "${server.name}" is in cooldown (HTTP ${cd.status ?? "n/a"}, ` +
`${Math.round(remaining / 1000)}s remaining): ${cd.message}`
);
}
function recordFailure(key: string, status: number | undefined, err: unknown): void {
const message = err instanceof Error ? err.message : String(err);
const cooldownMs = status === 429 ? extractRetryAfterMs(err) : DEFAULT_RATE_LIMIT_COOLDOWN_MS;
failureCooldown.set(key, {
until: Date.now() + cooldownMs,
status,
message,
});
}
export async function getClient(server: McpServerConfig, signal?: AbortSignal): Promise<Client> {
const key = keyOf(server);
const existing = pool.get(key);View on GitHub (pinned to fa13ee4ad6)