{"record":{"id":"3848107ad0f82134","repo":"musistudio/claude-code-router","slug":"toolhub-resolver-requires-toolhub-openai-api-key-a","errorCode":null,"errorMessage":"ToolHub resolver requires TOOLHUB_OPENAI_API_KEY and TOOLHUB_OPENAI_MODEL.","messagePattern":"ToolHub resolver requires TOOLHUB_OPENAI_API_KEY and TOOLHUB_OPENAI_MODEL\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/mcp/toolhub-mcp.ts","lineNumber":1482,"sourceCode":"    openAiModel?: string;\n  }) {}\n\n  async search(input: {\n    catalog: SearchCatalogItem[];\n    code?: string;\n    query: string;\n    timeoutMs?: number;\n    topK?: number;\n  }): Promise<SearchResult> {\n    const query = input.query.trim();\n    if (!query) {\n      throw new Error(\"ToolHub resolve query must be non-empty.\");\n    }\n    const apiKey = this.config.openAiApiKey || env(\"TOOLHUB_OPENAI_API_KEY\");\n    const baseURL = this.config.openAiBaseUrl || env(\"TOOLHUB_OPENAI_BASE_URL\") || \"https://api.openai.com/v1\";\n    const model = this.config.openAiModel || env(\"TOOLHUB_OPENAI_MODEL\");\n    if (!apiKey || !model) {\n      throw new Error(\"ToolHub resolver requires TOOLHUB_OPENAI_API_KEY and TOOLHUB_OPENAI_MODEL.\");\n    }\n\n    const topK = normalizeTopK(input.topK);\n    const timeoutMs = normalizeSearchTimeout(input.timeoutMs);\n    const deadlineAt = Date.now() + timeoutMs;\n    await waitForLocalResolverEndpoint(baseURL, apiKey, timeoutMs);\n    const client = new OpenAI({ apiKey, baseURL });\n    const messages: SearchMessage[] = [\n      {\n        role: \"user\",\n        content: JSON.stringify({\n          context: input.code ?? \"\",\n          query\n        }, null, 2)\n      }\n    ];\n\n    let didCallAnalyzer = false;","sourceCodeStart":1464,"sourceCodeEnd":1500,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/mcp/toolhub-mcp.ts#L1464-L1500","documentation":"Thrown when the ToolHub resolver (which uses an OpenAI-compatible chat model to map a natural-language query to catalog tools) has neither an API key nor a model configured. It checks config values first, then the TOOLHUB_OPENAI_API_KEY and TOOLHUB_OPENAI_MODEL environment variables, and throws if either is missing.","triggerScenarios":"Invoking resolve() without setting openAiApiKey/openAiModel in config and without both TOOLHUB_OPENAI_API_KEY and TOOLHUB_OPENAI_MODEL in the environment.","commonSituations":"Missing .env entries in local dev; CI/containers where ToolHub env vars aren't exported; only one of the two vars set (key but no model name); custom baseURL (e.g. local gateway) set but key/model forgotten.","solutions":["Set TOOLHUB_OPENAI_API_KEY and TOOLHUB_OPENAI_MODEL in the environment where the process runs","Or pass openAiApiKey and openAiModel explicitly in the ToolHub config object","For local/CCR gateways, also set TOOLHUB_OPENAI_BASE_URL and ensure the gateway is up"],"exampleFix":"// before\nconst toolhub = new ToolHubClient({});\n\n// after\nconst toolhub = new ToolHubClient({ openAiApiKey: process.env.TOOLHUB_OPENAI_API_KEY!, openAiModel: process.env.TOOLHUB_OPENAI_MODEL! });","handlingStrategy":"validation","validationCode":"const required = [\"TOOLHUB_OPENAI_API_KEY\", \"TOOLHUB_OPENAI_MODEL\"];\nconst missing = required.filter(k => !process.env[k]);\nif (missing.length) throw new Error(`Missing env: ${missing.join(\", \")}`);","typeGuard":null,"tryCatchPattern":"try {\n  await toolhub.resolve({ query });\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"TOOLHUB_OPENAI\")) {\n    // configuration problem: fail fast with clear ops message\n    failDeployment(e.message);\n  }\n  throw e;\n}","preventionTips":["Add a startup assertion for ToolHub env vars","Keep .env.example in sync with required ToolHub variables","Fail fast in CI when resolve is exercised without config"],"tags":["configuration","env-var","openai","toolhub"],"backgroundTag":"missing-env-var","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}