musistudio/claude-code-router · critical · Error

ToolHub resolver could not connect to CCR Gateway at ${readi

Error message

ToolHub resolver could not connect to CCR Gateway at ${readinessUrl}: ${formatError(lastError)}.

What it means

Thrown after the resolver exhausts its polling budget waiting for a local CCR Gateway (an OpenAI-compatible local proxy used by ToolHub resolve) to become ready at its readiness URL. The last observed connection error is included via formatError. This is a startup/dependency failure, not a query failure.

Source

Thrown at packages/core/src/mcp/toolhub-mcp.ts:1739

    const timer = setTimeout(() => controller.abort(), 1000);
    try {
      await fetch(readinessUrl, {
        headers: { authorization: `Bearer ${apiKey}` },
        method: "GET",
        signal: controller.signal
      });
      return;
    } catch (error) {
      lastError = error;
      if (!isRetryableLocalResolverError(error)) {
        return;
      }
      await delay(300);
    } finally {
      clearTimeout(timer);
    }
  }
  throw new Error(`ToolHub resolver could not connect to CCR Gateway at ${readinessUrl}: ${formatError(lastError)}.`);
}

function localResolverReadinessUrl(baseURL: string): string | undefined {
  let parsed: URL;
  try {
    parsed = new URL(baseURL);
  } catch {
    return undefined;
  }
  if (!isLoopbackHostname(parsed.hostname)) {
    return undefined;
  }
  const base = baseURL.replace(/\/+$/g, "");
  return `${base}/models`;
}

function isLoopbackHostname(hostname: string): boolean {
  const normalized = hostname.trim().toLowerCase().replace(/^\[|\]$/g, "");

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Start the CCR Gateway and confirm it listens at the TOOLHUB_OPENAI_BASE_URL port
  2. curl the readiness URL printed in the message to verify it returns 200
  3. Increase the resolve timeoutMs so slow gateway startups can converge
  4. Point TOOLHUB_OPENAI_BASE_URL directly at OpenAI (or another ready endpoint) if you don't need the local gateway

Example fix

# before
TOOLHUB_OPENAI_BASE_URL=http://localhost:9999/v1  # gateway not running

# after
ccr-gateway start &  # or correct port
curl http://localhost:9999/v1/health
TOOLHUB_OPENAI_BASE_URL=http://localhost:9999/v1
Defensive patterns

Strategy: retry

Validate before calling

async function waitGateway(url: string, timeoutMs = 30000) {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    try { if ((await fetch(url)).ok) return; } catch {}
    await new Promise(r => setTimeout(r, 300));
  }
  throw new Error(`Gateway not ready: ${url}`);
}
await waitGateway(readinessUrl);

Try / catch

try {
  await toolhub.resolve({ query });
} catch (e) {
  if (e instanceof Error && e.message.includes("could not connect to CCR Gateway")) {
    await startGateway(); await waitGateway(readinessUrl);
    return await toolhub.resolve({ query });
  }
  throw e;
}

Prevention

When it happens

Trigger: waitForLocalResolverEndpoint retries against the readiness endpoint derived from TOOLHUB_OPENAI_BASE_URL until timeout, and every attempt fails (connection refused, DNS failure, TLS error), so the throw fires with the last error embedded.

Common situations: CCR Gateway not started or still booting when resolve() is called; wrong port in TOOLHUB_OPENAI_BASE_URL; gateway bound to a different interface; firewall blocking localhost connections; readiness URL path not exposed by an older gateway version.

Related errors


AI-assisted analysis of musistudio/claude-code-router@99f24806c6 (2026-08-27). Data as JSON: /api/errors/d57d4dbf1f78d11f. Report an issue: GitHub.