Skip to content

Guide · Errors and retries

Agent tool error handling: a field guide for web fetch

Which web fetch failures to retry in code, which to hand to the model, and how to word the error so the agent picks a sensible next step instead of looping.

Guide

This article was drafted with AI assistance from the sources listed below and edited and published by Teneo Labs. Disclosure: this site is run by Teneo Labs, the company behind Live Web Data API, which this article mentions.

Who handles which failure
CriterionYour codeThe model
429 rate limitedWait for Retry-After, then retrySees it only if retries run out
TimeoutRetry once or twice with backoffPicks another source if it persists
Not found (404/410)No retryDrops the URL or searches again
Needs JavaScriptNo retryTries another page or tells the user
Restricted targetNo retryMoves on, does not try variants
Auth or out of creditsStop the task, alert a humanNever sees it

A web fetch fails more often than most tools an agent calls. Pages move, servers are slow, rate limits kick in, and some pages show nothing without JavaScript. None of this is unusual. What decides whether the agent copes is who handles each failure and what the model is told. This guide gives a taxonomy, a default split between code and model, and wording that works.

Two kinds of failure

Every failure falls into one of two groups.

  • Transient. The same call may succeed a moment later: rate limits (429), timeouts, server errors (5xx), dropped connections.
  • Permanent for this URL. Repeating the call returns the same answer: not found, gone, needs JavaScript, unsupported content type, a target the provider does not fetch.

A third group sits outside the loop: your own account problems, such as an invalid key or no credits left. The model cannot fix those, so it should never see them.

Retry in code, not in the prompt

When a model sees a 429, it usually calls the tool again at once. That is the worst possible reaction: it adds load, earns another 429 and spends tokens on every round. Retries of transient errors are mechanical, so do them in code:

  1. Retry only transient errors.
  2. Use exponential backoff with jitter, as the Amazon Builders' Library article describes, so parallel calls do not retry in lockstep.
  3. If the response has a Retry-After header, wait at least that long. MDN documents it as either a number of seconds or an HTTP date.
  4. Cap the attempts (three is a common default) and the total wait.
  5. Only when retries run out, return a tool result that tells the model the page is unavailable right now.
for attempt in range(3):
    try:
        return fetch(url)
    except RateLimited as e:
        sleep(e.retry_after or backoff(attempt))
    except Timeout:
        sleep(backoff(attempt))
return {"ok": False, "error": "TEMPORARILY_UNAVAILABLE",
        "message": "The page did not respond after 3 tries. Use another source or try later."}

Hand permanent failures to the model

A permanent failure is a decision point, and the model is the right one to make it: try another source, search again, ask the user or say it could not check. It needs two things: a stable tag your code can log and test, and one plain sentence it can act on.

Tag (example)Sentence for the model
CRAWL_NOT_FOUNDThe page does not exist (404 or 410). Do not retry; find another source.
CRAWL_NOT_RENDEREDThe page needs JavaScript to show its content. Try another page or tell the user.
UNSUPPORTED_CONTENT_TYPEThe URL is a file, not a web page. Use a tool for that file type.
RESTRICTED_TARGETThis site cannot be fetched. Do not try other URLs on it.
SOURCE_NOT_AVAILABLEThe site refused the request. Use another source.

A refusal or a restricted target is the site's or the provider's decision: do not retry with URL variants or other tools to get around it. Sites' robots.txt and terms of service, and the provider's Acceptable Use Policy, still apply. This is not legal advice.

Anthropic's guidance on tools makes the same point about errors in general: an error response should steer the agent toward a better next call, not just report a code.

Partial failures in multi-URL calls

When one call reads several URLs, one failure must not fail the rest. Return one status per URL, and let the model see which pages it has and which it does not. Count successes and failures separately in your logs; a run where 4 of 10 pages failed is a different run from one where all 10 succeeded, even if the answer looks fine.

Stop conditions

Retries inside one call are capped; the loop around them needs caps too:

  • a maximum number of fetches per task,
  • a maximum number of failed fetches per task (for example 5) after which the agent must answer with what it has,
  • no second attempt at the same URL with the same tag in one task.

Without these, an agent can walk a dead site one URL at a time until the step limit ends the run.

A worked example

Live Web Data API, made by the publisher of this site, uses this split. Every URL in a request gets a status with an error tag and message on failure, and failed, timed-out and restricted URLs cost 0 credits. The JavaScript and Python SDKs retry 429 (honoring Retry-After), 5xx and network errors up to three times with backoff and jitter, and never retry 400, 401, 402, 403 or 451. The MCP server returns errors as one actionable sentence ending in "No credits were charged." The full table of tags is in the API reference.

Where this breaks

  • Errors that look like success. A consent page or a soft 404 returns 200 with the wrong text. A fetch tool that extracts the main content helps, but check titles in your evals.
  • Retries that change meaning. Retrying a read is safe. Retrying a tool that sends, books or pays is not; keep those out of automatic retry entirely. See the prompt injection guide on separating read and act tools.
  • Cost of a long failure path. Every failed attempt the model sees costs tokens even when the fetch is free. The token budget guide covers per-task limits.

Frequently asked questions

Should the LLM decide when to retry a failed tool call?

Not for transient failures. Retrying 429s, timeouts and 5xx responses is mechanical and belongs in code with backoff and a cap. The model should decide only what a retry cannot fix: whether to use another source, ask the user or give up.

How many times should an agent retry a web fetch?

A common starting point is up to three attempts with exponential backoff and jitter for transient errors, and zero for errors a retry cannot change, such as not found or a restricted target. Add a per-task limit so retries across many calls cannot add up.

Should a tool error be an exception or a tool result?

A tool result, for anything the model can react to. An exception usually ends the run or reaches the model as a stack trace. Reserve exceptions for problems the model cannot fix, such as an invalid API key or an empty balance.

What should a tool error message say?

One sentence on what happened and one on what to do next, in plain words. For example: the page needs JavaScript to show its content; try the site's documentation page or tell the user. Leave out stack traces and raw HTTP bodies.

Sources

  1. 429 Too Many Requests (MDN) (Documentation)
  2. Retry-After header (MDN) (Documentation)
  3. Timeouts, retries and backoff with jitter (Amazon Builders' Library) (Blog)
  4. Writing effective tools for agents, with agents (Anthropic Engineering) (Blog)