musistudio/claude-code-router · warning

Custom router does not export a function: ${routerPath}

Error message

Custom router does not export a function: ${routerPath}

What it means

The gateway tried to load a custom router module configured for a model route, but the resolved module exports neither a function directly nor a default function. The router hook must be a function accepting (request, config, options) and returning a route selector string. When no function export is found the request falls back to default routing.

Source

Thrown at packages/core/src/gateway/claude-code-router-plugin.ts:213

  }

  getRouteDiagnostics(): RouteDiagnostic[] {
    return [...this.compiled.diagnostics];
  }

  private async resolveCustomRoute(request: MutableRequestLike): Promise<string | undefined> {
    const routerPath = this.config.CUSTOM_ROUTER_PATH;
    if (!routerPath) {
      return undefined;
    }

    try {
      const resolvedRouterPath = resolveCustomRouterModule(routerPath);
      delete requireFromHere.cache[resolvedRouterPath];
      const loaded = requireFromHere(resolvedRouterPath) as unknown;
      const customRouter = typeof loaded === "function" ? loaded : readDefaultFunction(loaded);
      if (!customRouter) {
        request.log.warn(`Custom router does not export a function: ${routerPath}`);
        return undefined;
      }
      const result = await customRouter(request, this.config, { event: this.event });
      return normalizeRouteSelector(typeof result === "string" ? result : undefined);
    } catch (error) {
      request.log.error(`Failed to load custom router "${routerPath}": ${formatError(error)}`);
      return undefined;
    }
  }
}

function resolveCustomRouterModule(routerPath: string): string {
  const resolved = requireFromHere.resolve(resolveLocalModulePath(routerPath, "Custom router"));
  assertJavaScriptModulePath(resolved, "Custom router");
  return resolved;
}

function resolveLocalModulePath(value: string, label: string): string {

View on GitHub (pinned to 99f24806c6)

Solutions

  1. Ensure the router module exports a function directly: module.exports = async (request, config, { event }) => "model-name"; or a default function: exports.default = async (...) => ...
  2. If using TypeScript/ESM, add a CommonJS-compatible default export or build to CJS
  3. Verify the configured routerPath resolves to the intended file and the module loads without throwing (a throw produces a different error but can mask export issues)
  4. Test the module in isolation: const m = require('./my-router'); console.log(typeof m, typeof m.default)

Example fix

// before
// my-router.js
export const router = async (request, config, { event }) => "claude-sonnet-4";

// after
// my-router.js
module.exports = async function router(request, config, { event }) {
  return "claude-sonnet-4";
};
Defensive patterns

Strategy: validation

Validate before calling

function validateRouterModule(path) {
  const loaded = require(path);
  const fn = typeof loaded === "function" ? loaded : loaded?.default;
  if (typeof fn !== "function") throw new Error(`Router ${path} must export a function`);
  return fn;
}

Type guard

const isRouterExport = (m: unknown): m is (req: unknown, cfg: unknown, o: unknown) => Promise<unknown> =>
  typeof m === "function" || typeof (m as { default?: unknown })?.default === "function";

Prevention

When it happens

Trigger: Setting a model's router to a module path (e.g. ./my-router.js or a marketplace module) whose file exports an object, constants, or nothing instead of module.exports = async (request, config, opts) => "model-id". Also happens when the file's default export is an object rather than a function.

Common situations: Writing a custom router with ESM-style named exports (export const route = ...) which CommonJS require() sees as an object; transpiling to a module with only named exports; typos in the file path causing a different module to load; default-exporting a class instance instead of a function.

Related errors


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