Skip to content

pass on why a provider failed, in its words or with what to do next, and say when a web page was cut - #165

Merged
hkonsti merged 1 commit into
mainfrom
hkonsti/exa-page-errors
Oct 30, 2026
Merged

hkonsti merged 1 commit into
mainfrom
hkonsti/exa-page-errors

Conversation

@hkonsti

@hkonsti hkonsti commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

These fix problems agents reported with send_feedback when reading pages through Exa, and errors that dropped what a provider said. Over the last 10 days about 6% of Exa page reads failed, and every one was logged as validation_error. Agents were told "Exa turned it down" with nothing on whether to retry.

Errors can carry a message. not_found and provider_unreachable take an optional message. When there is one, agents get it word for word. That holds over MCP and in the web app, where agentErrorMessage now uses the api's words for 404 and 502. Without one, they read as before ("The provider could not be reached. Try again in a moment."). The activity log still keeps only the kind and HTTP status.

Every page Exa couldn't read failed the same way. When Exa's MCP server can't read a page, it answers Error fetching URL(s): <url>: <code>. Each code it gives now becomes an error with the address and what to do next:

Exa's code Error kind What the agent is told
CRAWL_NOT_FOUND, CRAWL_HTTP_404, CRAWL_HTTP_410 not_found Nothing was found there. The page may have moved, or the crawler may not reach it. Search for its current address, or read another copy (a file's raw address instead of its page, or the other way round)
CRAWL_LIVECRAWL_TIMEOUT provider_unreachable The site was too slow. Try again in a few minutes
CRAWL_HTTP_429, CRAWL_HTTP_5xx provider_unreachable, with the status Too many requests, or the site is failing. Try again in a few minutes
CRAWL_HTTP_401, CRAWL_HTTP_403 validation_error The site turned the crawler away. Trying again won't help: find the content elsewhere, or ask the person
CRAWL_EMPTY_CONTENT validation_error No text: the page is empty, or built by JavaScript the crawler doesn't run
CRAWL_UNEXPECTED_CONTENT_TYPE validation_error The content isn't something Exa reads as text, such as an image

Anything else stays as it was. That includes CRAWL_UNKNOWN_ERROR (which Exa gives for an address that doesn't resolve), other statuses and unknown error. Parallel's web_fetch errors that come with a site status (404, 403, 429, 5xx) are mapped the same way. The messages live in connections/page-failure.ts, which Exa, Parallel and the web connector all use.

Empty pages were passed on as if they were the page. When Exa has nothing and no code for why, it answers "No content found for the provided URL(s)." without marking it as an error. That text used to come back as the page. Now it, and any page with no text from either provider, fails as above.

Long pages were cut without a word. Exa stops at maxCharacters and doesn't say so, and we cut Parallel's full content at 100,000 characters just as quietly. A cut page now ends with a note, as Cloudflare's long answers do since #151: [Cut short: the page is 4,992,566 characters long, of which these are the first 100,000. Read a part of it at its own address, …]. Parallel returns the whole page, so the note gives its length. Exa is asked for 101,000 characters. When more than 100,000 come back, the page goes on, and the note says so without a length. The extra 1,000 cover whitespace that gets trimmed where Exa cuts the text.

Other providers' errors.

  • Granola had no toolError. Its tool errors, such as one about something the account's plan doesn't include, became a bare "could not be reached, try again", so agents kept retrying. Granola's words are now passed on as "Granola turned it down: …". The match is generic, since we haven't seen Granola's exact wording.
  • Any other MCP tool error still becomes provider_unreachable when the server has no toolError, or its toolError doesn't recognise the error (Linear's and Notion's unrecognised failures). It now carries the tool's own text: "Linear answered with an error: …". A JSON-RPC error carries its message the same way. Tools write that text for the model, as any MCP client would show it. It goes to the agent only, never to the activity log.
  • Two answers where trying again would make a duplicate now say so. Notion's notion-create-pages answering without saying which page it made gets "look for it by its title before trying again". An IMAP server not saying which UID a saved draft got gets "look for it in the drafts before saving it again".

Testing

  • bun run typecheck, bun run test, bun run lint (one existing warning in agent-collection-page.tsx)
  • New tests:
    • each Exa code's error, and the codes kept as before
    • Parallel's status errors
    • an empty page from each provider
    • a cut page from Exa and from Parallel, each with its note
    • the MCP and web app wording when there is a message and when there isn't
    • Granola's tool errors
    • the mcp-client default for tool errors and JSON-RPC errors, with and without words
    • Notion's create answer without an id
  • Read Exa's MCP server source (src/tools/webFetch.ts) for its tool parameters and error format.
  • Called the keyless mcp.exa.ai/mcp through callMcpTool with the new EXA.toolError, using test addresses that returned 404, 403, 429, 503, a slow response, an image and an empty body. Each one mapped as in the table. An address that doesn't resolve stays as before. A long spec page came back at exactly 101,000 characters, so the cut is detected. Parallel's keyless web_fetch gave http_error with 404 and 403, and returned a 5-million-character page whole.
  • Not tried with a real Granola account on a free plan, so Granola's exact error text is untested.

…and say when a web page was cut

Exa's CRAWL_* codes and Parallel's site statuses become not found, out of reach for now or turned down, each with the address and what to try next; unknown codes stay as before. An empty page fails instead of reading as the page, and a page cut at 100,000 characters ends with a note saying so. not_found and provider_unreachable take an optional message for agents: Granola's tool errors, any MCP tool or JSON-RPC error without a toolError of its own, and two answers where trying again would make a duplicate, a Notion page and an IMAP draft, now use it.
@hkonsti hkonsti changed the title say why a web page couldn't be read and what to do next, and when it was cut pass on why a provider failed, in its words or with what to do next, and say when a web page was cut Oct 30, 2026
@hkonsti
hkonsti force-pushed the hkonsti/exa-page-errors branch from 97b2ca2 to aa48b2f Compare October 30, 2026 20:24
@hkonsti
hkonsti merged commit 1234ecc into main Oct 30, 2026
3 checks passed
@hkonsti
hkonsti deleted the hkonsti/exa-page-errors branch October 30, 2026 20:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant