Skip to content

docs: document NetworkTransportError and updated HTTP adapter routing#920

Merged
jottakka merged 3 commits intomainfrom
docs/network-transport-error
Apr 20, 2026
Merged

docs: document NetworkTransportError and updated HTTP adapter routing#920
jottakka merged 3 commits intomainfrom
docs/network-transport-error

Conversation

@jottakka
Copy link
Copy Markdown
Contributor

@jottakka jottakka commented Apr 16, 2026

Summary

Documents the new NetworkTransportError class and updated HTTP adapter routing landing in:

What's new

  • NetworkTransportError — sibling of UpstreamError under ToolExecutionError, for failures where no complete HTTP response was received (timeouts, connection errors, decoding, redirect exhaustion). Has status_code=None.
  • Three new ErrorKind values: NETWORK_TRANSPORT_RUNTIME_TIMEOUT, _UNREACHABLE, _UNMAPPED.
  • HTTP adapter now routes client-construction bugs (InvalidURL, UnsupportedProtocol, MissingSchema, SSLError, etc.) to FatalToolError instead of UpstreamError.

Files changed

File Change
app/_components/error-hierarchy.tsx Add NetworkTransportError row to the ASCII hierarchy diagram
app/en/references/mcp/python/errors/page.mdx Add NetworkTransportError entry and list the 3 new ErrorKind values
app/en/guides/create-tools/error-handling/useful-tool-errors/page.mdx Routing summary: UpstreamError vs NetworkTransportError vs FatalToolError for HTTP adapter
app/en/guides/tool-calling/error-handling/page.mdx Extend Python/JS/Java client examples with a NETWORK_TRANSPORT_ branch

Test plan

  • Visual review of the hierarchy diagram on both guide pages
  • Confirm code examples still render correctly
  • Verify the 3 new ErrorKind values are correctly linked
  • Cross-check with the linked arcade-mcp PRs for naming consistency

🤖 Generated with Claude Code


Note

Low Risk
Low risk doc-only changes updating error taxonomy and example branches; main risk is minor confusion if naming drifts from released client/server versions.

Overview
Documents the new NetworkTransportError as a sibling to UpstreamError for HTTP failures where no response is received, including its status_code=None behavior and the new ErrorKind classifications.

Updates the error-hierarchy diagram and the HTTP adapter routing guidance to distinguish real HTTP responses (UpstreamError/UpstreamRateLimitError) vs transport failures (NetworkTransportError) vs client construction bugs (FatalToolError), and extends Python/JS/Java client error-handling examples with a NETWORK_TRANSPORT_ branch.

Reviewed by Cursor Bugbot for commit b6e8614. Bugbot is set up for automated code reviews on this repo. Configure here.

Adds NetworkTransportError to the error hierarchy diagram and Python
reference, describes the three NETWORK_TRANSPORT_RUNTIME_* ErrorKind
values, and extends client-side error handling examples (Python, JS,
Java) with a NETWORK_TRANSPORT_ branch. Clarifies in the tool-author
guide how the HTTP error adapter now splits exceptions between
UpstreamError, NetworkTransportError, and FatalToolError.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Apr 20, 2026 11:41pm

Request Review

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style Review

Found 5 style suggestion(s).

Powered by Vale + Claude

Comment thread app/en/guides/create-tools/error-handling/useful-tool-errors/page.mdx Outdated
Comment thread app/en/references/mcp/python/errors/page.mdx Outdated
Comment thread app/en/references/mcp/python/errors/page.mdx Outdated
Comment thread app/en/references/mcp/python/errors/page.mdx Outdated
Comment thread app/en/references/mcp/python/errors/page.mdx Outdated
@jottakka jottakka requested a review from a team April 17, 2026 03:29
@jottakka jottakka self-assigned this Apr 17, 2026
@jottakka jottakka marked this pull request as ready for review April 17, 2026 16:01

- **Real HTTP responses** (4xx/5xx) → `UpstreamError` (with `status_code`). 429 becomes `UpstreamRateLimitError`.
- **No response received** (connect/read timeouts, DNS failures, connection errors, TLS handshake failures, decoding errors, redirect-loop exhaustion) → `NetworkTransportError` with `status_code=None`. Classified by `ErrorKind` as `NETWORK_TRANSPORT_RUNTIME_TIMEOUT`, `NETWORK_TRANSPORT_RUNTIME_UNREACHABLE`, or `NETWORK_TRANSPORT_RUNTIME_UNMAPPED`.
- **Client construction bugs** (`InvalidURL`, `UnsupportedProtocol`, `MissingSchema`, `InvalidSchema`, `InvalidHeader`, `InvalidProxyURL`, `URLRequired`, `SSLError`) → `FatalToolError`. These indicate a tool-authoring mistake or local TLS configuration issue and are not retryable.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs distinction between tool-authored URLs, schemas, headers, etc. and caller-provided values. In other words, is it really a tool-authoring mistake if the caller provides a invalid URL?

jottakka added a commit to ArcadeAI/arcade-mcp that referenced this pull request Apr 20, 2026
## Summary

Routes HTTP adapter exceptions to the right error class instead of
shoe-horning everything into `UpstreamError`. Addresses Eric's earlier
feedback that several exceptions this PR was wrapping as `UpstreamError`
didn't satisfy the "something happened with the upstream" claim (local
pool exhaustion, client-side request construction, local TLS failures).

### Scope

- `UpstreamError` (unchanged) — upstream responded with an HTTP status
code.
- **`NetworkTransportError`** (new sibling in `arcade-core`) — no
complete response was received. `status_code=None`. Three kinds:
`NETWORK_TRANSPORT_RUNTIME_TIMEOUT`, `_UNREACHABLE`, `_UNMAPPED`.
- **`FatalToolError`** (existing) — client construction bugs
(`InvalidURL`, `UnsupportedProtocol`, `MissingSchema`, `InvalidHeader`,
`LocalProtocolError`, …) and local TLS/cert config failures. Never
retried.

---

## Before / After (per Eric's request)

Shows the error payload a tool produces for each exception, before this
PR vs. after. "Before" = current `main` (exceptions without real HTTP
responses fall through to the generic `@tool` `FatalToolError` catch-all
with `message=str(exc)`).

### No-response transport failures

| Exception | Before — class / message / kind | After — class / message
/ kind |
|---|---|---|
| `httpx.PoolTimeout` | `FatalToolError` — `str(exc)` leaks raw detail —
`TOOL_RUNTIME_FATAL`, not retryable | `NetworkTransportError` — `"HTTP
request timed out before a complete response was received."` —
`NETWORK_TRANSPORT_RUNTIME_TIMEOUT`, **retryable** |
| `httpx.ConnectTimeout` | same as above | same as PoolTimeout —
`TIMEOUT`, retryable |
| `httpx.ConnectError` (refused / DNS) | `FatalToolError` — `str(exc)` |
`NetworkTransportError` — `"HTTP request failed before reaching the
upstream service."` — `UNREACHABLE`, retryable |
| `httpx.RemoteProtocolError` (upstream sent bad HTTP) |
`FatalToolError` — `str(exc)` | `NetworkTransportError` — same message
as ConnectError — `UNREACHABLE`, retryable |
| `httpx.DecodingError` | `FatalToolError` — `str(exc)` |
`NetworkTransportError` — `"HTTP response from upstream could not be
decoded."` — `UNMAPPED`, retryable |
| `httpx.TooManyRedirects` | `FatalToolError` — `str(exc)` |
`NetworkTransportError` — `"HTTP redirect limit exceeded before a final
response was received."` — `UNMAPPED`, **not** retryable |

### Client construction / local env bugs

| Exception | Before | After |
|---|---|---|
| `httpx.UnsupportedProtocol`, `httpx.InvalidURL`,
`httpx.LocalProtocolError` | `FatalToolError` with `message=str(exc)`
(may leak scheme / URL content) | `FatalToolError` — `"Tool constructed
an invalid HTTP request — likely a tool-authoring bug."` —
`TOOL_RUNTIME_FATAL`, not retryable |
| `requests.MissingSchema`, `InvalidURL`, `InvalidHeader`,
`InvalidSchema`, `InvalidProxyURL`, `URLRequired` | same as above | same
as above |
| `requests.SSLError` | `FatalToolError` — `str(exc)` often contains raw
cert chain detail | `FatalToolError` — `"TLS handshake failed — likely a
local certificate or trust configuration issue."` —
`TOOL_RUNTIME_FATAL`, not retryable |

### Real HTTP response errors (UNCHANGED — same behavior)

| Exception | Class | Message | Kind | Retryable |
|---|---|---|---|---|
| `httpx.HTTPStatusError` 404 | `UpstreamError` | `"Upstream HTTP
request failed (Not Found, client error)."` |
`UPSTREAM_RUNTIME_NOT_FOUND` | No |
| `httpx.HTTPStatusError` 429 (w/ Retry-After: 60) |
`UpstreamRateLimitError` | `"Upstream HTTP request failed (Too Many
Requests, client error). Retry after 60 second(s)."` |
`UPSTREAM_RUNTIME_RATE_LIMIT` | Yes |
| `httpx.HTTPStatusError` 500 | `UpstreamError` | `"Upstream HTTP
request failed (Internal Server Error, server error)."` |
`UPSTREAM_RUNTIME_SERVER_ERROR` | Yes |

### What's no longer in the message

- Raw exception `str(exc)` output (which frequently includes the full
URL with query-string tokens, connection pool details, or cert chains)
is **no longer the agent-facing `message`**. It's preserved in
`developer_message` for server-side diagnostics.
- The misleading "Upstream HTTP…" prefix is gone from network-transport
and construction-bug messages. Those messages now honestly describe what
happened on the tool side.
- For 429s without a `Retry-After` header, we still show "Retry after N
seconds." (pre-existing behavior; see follow-up notes).

---

## Companion PRs

-
[#823](#823)
— introduces `NetworkTransportError` in `arcade-core`
- [ArcadeAI/monorepo#911](ArcadeAI/monorepo#911)
— adds the 3 `ErrorKind` constants to the Go engine and Datadog
dashboards
- [ArcadeAI/docs#920](ArcadeAI/docs#920) —
documents the new hierarchy and adapter routing

## Follow-ups (out of scope for this PR)

A short investigation surfaced several pre-existing issues that are
worth fixing separately. A full list is in
`NETWORK_TRANSPORT_ERROR_FOLLOWUPS.md` (shared offline). Summary:

1. `requests.HTTPError` with `response is None` returns `None` from the
adapter; should fall through to the `NetworkTransportError(UNMAPPED)`
fallback instead of becoming a generic `FatalToolError`.
2. `developer_message` can leak URL query strings (and therefore tokens)
since it stores raw `str(exc)`.
3. `_sanitize_uri` does not strip userinfo (credentials in URL path).
4. `_parse_retry_ms` misinterprets epoch-style `x-ratelimit-reset`
headers.
5. 429 responses without `Retry-After` synthesize a fabricated "Retry
after 1 second(s)." suffix.
6. `UPSTREAM_RUNTIME_VALIDATION_ERROR` is defined but never emitted.
7. `UpstreamError` silently accepts out-of-range status codes.
8. `requests.HTTPError` branch re-extracts `request_url` /
`request_method` inconsistently (dead work).

## Test plan

- [x] Existing `libs/tests/sdk/test_httpx_adapter.py` +
`test_graphql_adapter.py` updated; every no-response / construction-bug
test asserts the new class + kind + `can_retry`.
- [x] Full test suite passes locally.
- [x] mypy clean on `arcade-core`, `arcade-tdk`, `arcade-mcp-server`.
- [x] Smoke-tested 21 exception routing cases end-to-end against real
httpx / requests exceptions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes core error classification and retryability for
`httpx`/`requests`/GraphQL transport failures, which can affect tool
retry behavior and telemetry. Risk is mitigated by extensive new/updated
tests covering the new mappings and privacy expectations.
> 
> **Overview**
> **Improves error adapter behavior to be more semantically correct and
privacy-safe.** The HTTP adapter now distinguishes real HTTP responses
(`UpstreamError`/`UpstreamRateLimitError`) from no-response failures
(`NetworkTransportError` with `ErrorKind` + retryability) and from
client construction/local TLS issues (`FatalToolError`).
> 
> **Reduces sensitive data exposure in agent-facing messages.**
Status-based errors now emit standardized messages derived from status
phrase/class, while preserving raw exception detail in
`developer_message`; Google/Microsoft/Slack fallback paths similarly
switch to `unhandled <ExceptionType>` messages and move `str(exc)` into
`developer_message`. GraphQL transport connection/protocol errors are
reclassified from `UpstreamError` (502) to `NetworkTransportError`, and
transport/server messages are standardized.
> 
> Bumps `arcade-tdk` version to `3.8.0` and expands/updates the SDK test
suite to assert new classes, `kind`, `can_retry`, request metadata
extraction, and privacy behavior.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1041cb1. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
jottakka and others added 2 commits April 20, 2026 20:38
…ting

- Active voice for HTTP adapter sentence (Google.Passive)
- Replace e.g. with 'for example' (Google.Latin)
- Remove spaces around em dashes (Google.EmDash)
- Clarify FatalToolError note: caller-provided values also routed here if unvalidated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@jottakka jottakka enabled auto-merge (squash) April 20, 2026 23:41
@jottakka jottakka merged commit 64d6256 into main Apr 20, 2026
9 checks passed
@jottakka jottakka deleted the docs/network-transport-error branch April 21, 2026 01:22
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.

2 participants