thedotmack/claude-mem · error · ServerClassifiedProviderError

auth_invalid

auth_invalid

Error message

OpenRouter API key not configured

What it means

The OpenRouter provider's constructor rejects instantiation when `options.apiKey` is falsy, throwing a ServerClassifiedProviderError with kind 'auth_invalid'. OpenRouter requires a Bearer key for every request, so the provider fails fast rather than sending doomed requests. Raised from the constructor, so the app crashes at wiring time.

Solutions

  1. Set OPENROUTER_API_KEY in the environment or deployment secrets
  2. Trace where the options object is built and confirm the correct env var/config key is read and non-empty
  3. Add a startup config check that fails with a clear message listing required provider keys
  4. Verify .env loading (dotenv/similar) runs before any provider construction

Example fix

// before
const provider = new OpenRouterObservationProvider({ apiKey: config.openrouter?.key });

// after
const apiKey = process.env.OPENROUTER_API_KEY;
if (!apiKey) throw new Error('OPENROUTER_API_KEY is required');
const provider = new OpenRouterObservationProvider({ apiKey });
Defensive patterns

Strategy: validation

Validate before calling

const apiKey = process.env.OPENROUTER_API_KEY;
if (typeof apiKey !== 'string' || apiKey.trim() === '') {
  throw new Error('OPENROUTER_API_KEY must be set to a non-empty string');
}
const provider = new OpenRouterObservationProvider({ apiKey });

Type guard

function hasOpenRouterKey(o: OpenRouterObservationProviderOptions): o is OpenRouterObservationProviderOptions & { apiKey: string } {
  return typeof o.apiKey === 'string' && o.apiKey.trim().length > 0;
}

Try / catch

try {
  provider = new OpenRouterObservationProvider({ apiKey });
} catch (e) {
  if (e instanceof ServerClassifiedProviderError && e.kind === 'auth_invalid') {
    throw new ConfigError('OpenRouter provider misconfigured: apiKey missing. Set OPENROUTER_API_KEY.');
  }
  throw e;
}

Prevention

When it happens

Trigger: Constructing `new OpenRouterObservationProvider({...})` with `apiKey` undefined, null, or empty string — typically because the OPENROUTER_API_KEY environment variable is unset or the wrong config key was read.

Common situations: OPENROUTER_API_KEY not set in the deployment environment; .env file not loaded; key stored under a different env name than the code reads; secrets manager returning undefined for a missing entry; refactoring renamed the option field without updating call sites.

Understand the failure class

Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/ba9457800973d837. Report an issue: GitHub.

Appendix: source

Thrown at src/server/generation/providers/OpenRouterObservationProvider.ts:55

interface OpenRouterResponse {
  choices?: Array<{ message?: { content?: string } }>;
  usage?: { total_tokens?: number };
  error?: { code?: string | number; message?: string };
}

export class OpenRouterObservationProvider implements ServerGenerationProvider {
  readonly providerLabel = 'openrouter' as const;
  private readonly apiKey: string;
  private readonly model: string;
  private readonly apiUrl: string;
  private readonly maxOutputTokens: number;
  private readonly siteUrl: string;
  private readonly appName: string;
  private readonly fetchImpl: typeof fetch;

  constructor(options: OpenRouterObservationProviderOptions) {
    if (!options.apiKey) {
      throw new ServerClassifiedProviderError('OpenRouter API key not configured', {
        kind: 'auth_invalid',
        cause: new Error('apiKey is required'),
      });
    }
    this.apiKey = options.apiKey;
    // Model is passed verbatim so arbitrary OpenAI-compatible ids work. #2393.
    this.model = options.model ?? DEFAULT_MODEL;
    this.apiUrl = resolveOpenRouterChatCompletionsUrl(options.baseUrl);
    this.maxOutputTokens = options.maxOutputTokens ?? 4096;
    this.siteUrl = options.siteUrl ?? OPENROUTER_APP_URL;
    this.appName = options.appName ?? OPENROUTER_APP_TITLE;
    this.fetchImpl = options.fetchImpl ?? fetch;
  }

  async generate(
    context: ServerGenerationContext,
    signal?: AbortSignal,
  ): Promise<ServerGenerationResult> {

View on GitHub (pinned to d8bc9755e7)