Error recovery

Every Pipeworx tool error is structured for self-correcting retries. Agents that read _meta recover from most failures without escalation.

What errors look like

{
  "result": {
    "content": [{ "type": "text", "text": "{\"error\":\"tool_error\",\"message\":\"Missing required parameter: country_code (string)\"}" }],
    "_meta": {
      "alternatives": [
        { "tool": "list_countries", "reason": "Find a valid country_code first" }
      ],
      "examples": [
        { "country_code": "US" },
        { "country_code": "GB" }
      ],
      "retry_hint": "Retry the same tool with arguments matching one of the example shapes.",
      "feedback_hint": "If this looks like a bug in is_today_holiday, file pipeworx_feedback({type:\"bug\", ...}).",
      "tier": "anonymous",
      "cost": { "total": 1, "components": [...] }
    }
  }
}

The standard agent recovery loop:

  1. Parse the text content as JSON; if it has error, treat as failure.
  2. Read _meta.examples — pattern-match the failed args against a working example. Most of the time this is enough.
  3. If you still don’t know how to recover, look at _meta.alternatives — these are tools that solve a related need.
  4. Use _meta.retry_hint as natural-language guidance to the model.
  5. If the failure looks like a Pipeworx-side issue, the feedback_hint gives you a pre-formatted pipeworx_feedback call.

Error classes

Pipeworx classifies errors at the gateway in _meta:

ClassMeaningShould agent retry?
errorReal tool failureYes — read _meta.examples and try again with corrected args
auth_requiredMissing API key for a BYO-key toolNo — surface to user; pass the key argument named in the error’s message (see below)
user_errorInvalid args (missing required, wrong types)Yes — read _meta.examples
upstream_throttledUpstream rate-limited (429) or blocked (403)Wait + retry, or use _meta.alternatives
unknown toolThe tool name doesn’t exist (-32602, Unknown tool: <name>)Not with the same name — see If a tool name doesn’t work

The class isn’t directly exposed in _meta (it’s used for analytics), but you can usually infer it from the error message.

Two exceptions worth knowing, because both will otherwise cost you a retry loop:

  • Unknown tool carries no retry_hint and no alternatives. Every other error does. The name is what failed, so re-sending it with different arguments cannot help — call discover_tools({task}) to find the real name instead.
  • On auth_required, trust the message, not the signup_hint. The key argument is per-tool: attom_avm reads _apiKey, altos_market_stats reads _altosKey. The generic signup_hint currently says _apikey (lowercase) for every tool, and since JSON arguments are case-sensitive, passing that name gets your key silently dropped and the same “API key required” error back. Verified live 2026-08-10; tracked as a fix, but read the message until it lands.

Pattern: self-correcting on schema mismatch

The most common error is “wrong arg shape.” _meta.examples is the same examples returned in tools/list — repeated here so the model has them in immediate context.

// First attempt
ct_get_study({})
// → error: "invalid_arguments"
// → message: "ct_get_study received an invalid argument: nct_id is required
//             but was not provided. Required: nct_id. Retry with all required
//             arguments — see _meta.examples for a valid shape."
// → _meta.examples: [{ "nct_id": "NCT04280705" }]

// Retry with corrected shape
ct_get_study({ nct_id: "NCT04280705" })
// → success

Pattern: zero results is not an error

The failure that costs you most isn’t the one that throws. A search tool that finds nothing usually succeeds — you get a well-formed response with an empty list, no error field, and nothing for the recovery loop above to catch:

fda_drug_events({ query: "asparafalcomine" })  // not a real drug
// → { "total": 0, "skip": 0, "limit": 0, "results": [] }
// → HTTP 200. No error. No retry_hint. No alternatives.

Nothing is broken here — the query ran and the answer is genuinely “no records.” But if your code only branches on error, this sails through as a result, and a model that treats an empty list as “no adverse events reported” has just told someone a drug is clean when the truth is that the drug name was misspelled.

So check for emptiness explicitly, and treat it as a question about your input rather than an answer about the world:

const r = fda_drug_events({ query: term });
if (!r.results?.length) {
  // Is the TERM wrong, or is the record genuinely absent? Resolve it first.
  const resolved = rxnorm_search({ name: term });
  // If the name resolves, the empty result is real — say "no records found".
  // If it doesn't resolve either, the term is wrong — say that instead.
}

The distinction matters because the two cases warrant opposite answers, and only one of them is a fact about the drug.

Pattern: feedback loop on unfixable errors

If you exhaust retries and alternatives, file:

pipeworx_feedback({
  type: "data_gap",
  message: "ct_get_study returned 'Cannot read properties' for valid-looking NCT IDs (NCT09999999); seems to crash on missing studies instead of returning a not-found error.",
  context: { tool: "ct_get_study", pack: "clinicaltrials" }
})

The Pipeworx team reviews feedback daily. Recurring patterns become bug fixes.

What you should NOT do

  • Don’t retry the exact same call without reading _meta.examples. If the call failed once with these args, it’ll fail again.
  • Don’t silently swallow errors. Surface them to the user with the tool name + the actual error message. Pipeworx errors are informative; hiding them makes debugging harder.
  • Don’t loop infinitely. Hard-cap retries at 3 per tool per task. After that, fall back to a different tool or escalate.

Last reviewed August 10, 2026