{"record":{"id":"8c8f9540f8f4fce9","repo":"musistudio/claude-code-router","slug":"toolhub-resolve-query-must-be-non-empty","errorCode":null,"errorMessage":"ToolHub resolve query must be non-empty.","messagePattern":"ToolHub resolve query must be non-empty\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/core/src/mcp/toolhub-mcp.ts","lineNumber":1476,"sourceCode":"class OpenAiToolHubSearchAgent {\n  private readonly analyzer = new ToolReferenceAnalyzer();\n\n  constructor(private readonly config: {\n    openAiApiKey?: string;\n    openAiBaseUrl?: string;\n    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 ?? \"\",","sourceCodeStart":1458,"sourceCodeEnd":1494,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/mcp/toolhub-mcp.ts#L1458-L1494","documentation":"Thrown by the ToolHub resolve flow when the natural-language query, after trimming, is empty. The resolver cannot search a catalog without a query, so this is a fast-fail input validation before any network or LLM work occurs.","triggerScenarios":"Calling the ToolHub resolve API with a query of \"\", \"   \", or a value that trims to empty (undefined coerced/typed as empty string).","commonSituations":"Passing an unvalidated user prompt straight through; template strings that interpolate undefined; whitespace-only input from a CLI arg or form field.","solutions":["Ensure the caller passes a non-empty, meaningful natural-language query","Trim and check user input before calling resolve","Add an upstream UI/CLI guard rejecting empty prompts"],"exampleFix":"// before\nawait toolhub.resolve({ query: userPrompt /* may be \"\" */ });\n\n// after\nconst q = (userPrompt ?? \"\").trim();\nif (!q) throw new Error(\"Prompt is required\");\nawait toolhub.resolve({ query: q });","handlingStrategy":"validation","validationCode":"const query = String(input.query ?? \"\").trim();\nif (!query) {\n  throw new Error(\"Query is required\");\n}\nawait toolhub.resolve({ query });","typeGuard":"const hasNonEmptyQuery = (q: unknown): q is string =>\n  typeof q === \"string\" && q.trim().length > 0;","tryCatchPattern":null,"preventionTips":["Trim user input at the boundary (CLI/UI) and reject empty prompts","Type resolve input as { query: string } so empties surface at compile/test time"],"tags":["validation","input","toolhub","empty-string"],"backgroundTag":"empty-required-parameter","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}