musistudio/claude-code-router · error

OpenRouter model id must be author/slug: ${model}

Error message

OpenRouter model id must be author/slug: ${model}

What it means

OpenRouter model ids must have the form author/slug (exactly one slash separating vendor and model). loadModelEndpoints splits on "/" and requires both parts; anything else (no slash, leading slash, empty slug) is rejected before any HTTP call.

Source

Thrown at packages/core/src/plugins/built-ins/openrouter-discount-provider-router.ts:323

  }
  const failure = endpointFailureCache.get(cacheKey);
  if (failure) {
    const cooldownAge = now - failure.failedAt;
    if (cooldownAge < endpointFailureCooldownMs) {
      const remainingMs = endpointFailureCooldownMs - cooldownAge;
      throw new Error(`OpenRouter endpoints request is cooling down after failure (${Math.ceil(remainingMs / 1000)}s left): ${failure.message}`);
    }
    endpointFailureCache.delete(cacheKey);
  }
  const inflight = endpointInflight.get(cacheKey);
  if (inflight) {
    return inflight;
  }

  const [author, ...slugParts] = model.split("/");
  const slug = slugParts.join("/");
  if (!author || !slug) {
    throw new Error(`OpenRouter model id must be author/slug: ${model}`);
  }

  const url = `${trimRight(apiRoot, "/")}/api/v1/models/${encodeURIComponent(author)}/${encodeURIComponent(slug)}/endpoints`;
  const promise = fetchModelEndpoints(url).then((endpoints) => {
    endpointFailureCache.delete(cacheKey);
    if (endpoints.length > 0) {
      endpointCache.set(cacheKey, { endpoints, fetchedAt: Date.now() });
      pruneEndpointCache();
    }
    return endpoints;
  }).catch((error) => {
    endpointFailureCache.set(cacheKey, {
      failedAt: Date.now(),
      message: formatError(error)
    });
    pruneEndpointFailureCache();
    throw error;
  }).finally(() => {

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Use the full author/slug id from OpenRouter's model listing
  2. Validate the id format before calling
  3. Trim whitespace/slashes from config-provided model ids

Example fix

// before
loadModelEndpoints("gpt-4o");
// after
loadModelEndpoints("openai/gpt-4o");
Defensive patterns

Strategy: validation

Validate before calling

const parts = model.split("/");
if (parts.length < 2 || !parts[0] || parts.slice(1).join("/") === "") throw new TypeError(`bad model id: ${model}`);

Type guard

const isOpenRouterModelId = (m: string): boolean => { const [a, ...rest] = m.split("/"); return Boolean(a && rest.join("/")); };

Try / catch

try { await loadModelEndpoints(model); } catch (e) { if (e instanceof Error && e.message.includes("must be author/slug")) return badRequest(); throw e; }

Prevention

When it happens

Trigger: Passing a bare model name like "gpt-4o", "openai/" (empty slug), or a multi-segment path where the slug ends up empty.

Common situations: Users pasting OpenAI-style model names, or ids constructed by joining config values where one part is empty.

Related errors


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