JuliusBrussee/caveman · error

tool search failed with HTTP

Error message

tool search failed with HTTP ${response.status}

What it means

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.

Solutions

  1. Log/check response.status to identify the cause; fix auth headers if 401/403
  2. Validate the catalog and request body if 400/422 (e.g. schema issues, too many tools)
  3. Retry with backoff on 429/5xx transient failures
  4. Verify the Cave backend URL and that the tool-search service is healthy

Example fix

// before
const result = await searchTools(cave, catalog, query);
// after
try {
  const result = await searchTools(cave, catalog, query);
} catch (e) {
  if (String(e).includes("HTTP 429")) await sleep(backoff);
  else throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!cave?.baseUrl) throw new Error("cave client not configured before tool search");
if (catalog.length === 0) throw new Error("tool catalog is empty; search would be rejected");

Type guard

null

Try / catch

try {
  const result = await searchTools(cave, catalog, query);
} catch (e) {
  const m = /HTTP (\d+)/.exec(String(e.message));
  if (m && (m[1] === "429" || m[1].startsWith("5"))) { await retryWithBackoff(); }
  else if (m && (m[1] === "401" || m[1] === "403")) { refreshCredentials(); }
  else throw e;
}

Prevention

When it happens

Trigger: 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+).

Common situations: Expired or wrong Cave credentials; oversized catalog payload rejected by server; tool-search service temporarily down; network proxy returning error pages.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/c385930afd714ebd. Report an issue: GitHub.

Appendix: source

Thrown at packages/sdk/typescript/src/index.ts:1610

  };
  if (options?.context !== undefined) body["context"] = options.context;
  if (options?.maxTools !== undefined) body["max_tools"] = options.maxTools;
  if (options?.toolSessionId !== undefined) body["session_id"] = options.toolSessionId;
  // Embedding-similarity is a pure passthrough: the SDK forwards the ranker
  // choice and the gateway honors "embeddings" only when it has an embedding
  // provider wired. The SDK never computes similarity itself (byte-safe, no
  // extra deps).
  if (options?.ranker !== undefined) body["ranker"] = options.ranker;

  const workflow = options?.workflow ?? cave.options.defaultWorkflow ?? "unlabeled-workflow";
  const response = await caveFetch(cave, `${cave.options.baseURL}/sdk/v1/tool-search`, {
    method: "POST",
    headers: headers(cave, workflow),
    body: JSON.stringify(body)
  });
  if (!response.ok) {
    discardResponse(response);
    throw new Error(`tool search failed with HTTP ${response.status}`);
  }
  const data = await response.json();

  // Map snake_case response to camelCase
  const rawSent = strictNonNegativeInt(data.sent_schema_tokens);
  const rawFull = strictNonNegativeInt(data.full_schema_tokens);
  const validCounts = rawSent !== null && rawFull !== null && rawSent <= rawFull;
  const sentSchemaTokens = validCounts ? rawSent : 0;
  const fullSchemaTokens = validCounts ? rawFull : 0;
  const savedTokens = fullSchemaTokens - sentSchemaTokens;
  const reductionPct = fullSchemaTokens === 0 ? 0 : Math.round((savedTokens / fullSchemaTokens) * 1000) / 10;

  const catalogByName = new Map<string, CaveTool>();
  for (const tool of catalog) {
    if (typeof tool.name !== "string" || tool.name.trim() === "" || catalogByName.has(tool.name)) {
      throw new Error("tool catalog names must be non-empty and unique");
    }
    catalogByName.set(tool.name, tool);

View on GitHub (pinned to 3ee70a1026)