{"record":{"id":"c385930afd714ebd","repo":"JuliusBrussee/caveman","slug":"tool-search-failed-with-http-response-status","errorCode":null,"errorMessage":"tool search failed with HTTP ${response.status}","messagePattern":"tool search failed with HTTP (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/sdk/typescript/src/index.ts","lineNumber":1610,"sourceCode":"  };\n  if (options?.context !== undefined) body[\"context\"] = options.context;\n  if (options?.maxTools !== undefined) body[\"max_tools\"] = options.maxTools;\n  if (options?.toolSessionId !== undefined) body[\"session_id\"] = options.toolSessionId;\n  // Embedding-similarity is a pure passthrough: the SDK forwards the ranker\n  // choice and the gateway honors \"embeddings\" only when it has an embedding\n  // provider wired. The SDK never computes similarity itself (byte-safe, no\n  // extra deps).\n  if (options?.ranker !== undefined) body[\"ranker\"] = options.ranker;\n\n  const workflow = options?.workflow ?? cave.options.defaultWorkflow ?? \"unlabeled-workflow\";\n  const response = await caveFetch(cave, `${cave.options.baseURL}/sdk/v1/tool-search`, {\n    method: \"POST\",\n    headers: headers(cave, workflow),\n    body: JSON.stringify(body)\n  });\n  if (!response.ok) {\n    discardResponse(response);\n    throw new Error(`tool search failed with HTTP ${response.status}`);\n  }\n  const data = await response.json();\n\n  // Map snake_case response to camelCase\n  const rawSent = strictNonNegativeInt(data.sent_schema_tokens);\n  const rawFull = strictNonNegativeInt(data.full_schema_tokens);\n  const validCounts = rawSent !== null && rawFull !== null && rawSent <= rawFull;\n  const sentSchemaTokens = validCounts ? rawSent : 0;\n  const fullSchemaTokens = validCounts ? rawFull : 0;\n  const savedTokens = fullSchemaTokens - sentSchemaTokens;\n  const reductionPct = fullSchemaTokens === 0 ? 0 : Math.round((savedTokens / fullSchemaTokens) * 1000) / 10;\n\n  const catalogByName = new Map<string, CaveTool>();\n  for (const tool of catalog) {\n    if (typeof tool.name !== \"string\" || tool.name.trim() === \"\" || catalogByName.has(tool.name)) {\n      throw new Error(\"tool catalog names must be non-empty and unique\");\n    }\n    catalogByName.set(tool.name, tool);","sourceCodeStart":1592,"sourceCodeEnd":1628,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/sdk/typescript/src/index.ts#L1592-L1628","documentation":"HTTP error from the SDK tool-search call: the gateway returned a non-success status for the tool discovery/search request. The status code is included; the failure is server/gateway-side, not a malformed request from the SDK.","triggerScenarios":"POST to the tool-search endpoint returns 4xx/5xx: bad API key (401/403), malformed body (400/422), rate limiting (429), or server errors (500+).","commonSituations":"Expired or wrong Cave credentials; oversized catalog payload rejected by server; tool-search service temporarily down; network proxy returning error pages.","solutions":["Log/check response.status to identify the cause; fix auth headers if 401/403","Validate the catalog and request body if 400/422 (e.g. schema issues, too many tools)","Retry with backoff on 429/5xx transient failures","Verify the Cave backend URL and that the tool-search service is healthy"],"exampleFix":"// before\nconst result = await searchTools(cave, catalog, query);\n// after\ntry {\n  const result = await searchTools(cave, catalog, query);\n} catch (e) {\n  if (String(e).includes(\"HTTP 429\")) await sleep(backoff);\n  else throw e;\n}","handlingStrategy":"try-catch","validationCode":"if (!cave?.baseUrl) throw new Error(\"cave client not configured before tool search\");\nif (catalog.length === 0) throw new Error(\"tool catalog is empty; search would be rejected\");","typeGuard":"null","tryCatchPattern":"try {\n  const result = await searchTools(cave, catalog, query);\n} catch (e) {\n  const m = /HTTP (\\d+)/.exec(String(e.message));\n  if (m && (m[1] === \"429\" || m[1].startsWith(\"5\"))) { await retryWithBackoff(); }\n  else if (m && (m[1] === \"401\" || m[1] === \"403\")) { refreshCredentials(); }\n  else throw e;\n}","preventionTips":["Check credentials and backend URL before deploying","Keep catalog payloads within server limits","Implement retry with backoff for 429/5xx","Monitor the tool-search service health"],"tags":["network","http","api","tool-search"],"backgroundTag":"http-error-response","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}