diff --git a/app/_components/error-hierarchy.tsx b/app/_components/error-hierarchy.tsx index 625e71934..df6f96f1a 100644 --- a/app/_components/error-hierarchy.tsx +++ b/app/_components/error-hierarchy.tsx @@ -19,6 +19,7 @@ ToolkitError # (Abstract base class) ├── RetryableToolError # Tool can be retried with extra context ├── ContextRequiredToolError # Additional context needed before retry ├── FatalToolError # Unhandled bugs in the tool implementation + ├── NetworkTransportError # No HTTP response received (timeout, DNS, TLS, etc.) └── UpstreamError # HTTP/API errors from external services └── UpstreamRateLimitError # Rate limiting errors from service `} diff --git a/app/en/guides/create-tools/error-handling/useful-tool-errors/page.mdx b/app/en/guides/create-tools/error-handling/useful-tool-errors/page.mdx index 7d408daf1..3f433cbcb 100644 --- a/app/en/guides/create-tools/error-handling/useful-tool-errors/page.mdx +++ b/app/en/guides/create-tools/error-handling/useful-tool-errors/page.mdx @@ -40,6 +40,12 @@ def fetch_data( return response.json() ``` +The HTTP error adapter routes exceptions from `httpx` and `requests` based on whether the client received a complete response: + +- **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 are not retryable and typically indicate a tool-authoring mistake (hardcoded invalid URL, schema, or header) or a local TLS configuration issue. If your tool accepts caller-provided URLs or headers and passes them directly to the HTTP client without validation, invalid caller input is also routed here. Validate caller-supplied values before use. + ### Explicit error adapters For tools using specific SDKs, you can specify error adapters explicitly: diff --git a/app/en/guides/tool-calling/error-handling/page.mdx b/app/en/guides/tool-calling/error-handling/page.mdx index ce5a2f65b..2faf975c2 100644 --- a/app/en/guides/tool-calling/error-handling/page.mdx +++ b/app/en/guides/tool-calling/error-handling/page.mdx @@ -60,6 +60,11 @@ def handle_tool_error(error: OutputError) -> None: elif error_kind.startswith("UPSTREAM_"): # The tool encountered an error from an upstream service print(error.message) + elif error_kind.startswith("NETWORK_TRANSPORT_"): + # The HTTP request never received a complete response (timeout, DNS + # failure, TLS handshake, etc.). These are typically transient and + # safe to retry. + print(error.message) client = Arcade() # Automatically finds the `ARCADE_API_KEY` env variable @@ -124,6 +129,11 @@ function handleToolError(error) { } else if (errorKind.startsWith("UPSTREAM_")) { // The tool encountered an error from an upstream service console.log(error.message); + } else if (errorKind.startsWith("NETWORK_TRANSPORT_")) { + // The HTTP request never received a complete response (timeout, DNS + // failure, TLS handshake, etc.). These are typically transient and + // safe to retry. + console.log(error.message); } } @@ -203,6 +213,11 @@ import org.slf4j.LoggerFactory; } else if (errorKind.asString().startsWith("UPSTREAM_")) { // The tool encountered an error from an upstream service logger.error(error.message()); + } else if (errorKind.asString().startsWith("NETWORK_TRANSPORT_")) { + // The HTTP request never received a complete response (timeout, DNS + // failure, TLS handshake, etc.). These are typically transient and + // safe to retry. + logger.error(error.message()); } } diff --git a/app/en/references/mcp/python/errors/page.mdx b/app/en/references/mcp/python/errors/page.mdx index 53dfdb05a..cd6430465 100644 --- a/app/en/references/mcp/python/errors/page.mdx +++ b/app/en/references/mcp/python/errors/page.mdx @@ -145,6 +145,14 @@ Error from an upstream service the tool depends on. Rate limit error from an upstream service. +### `NetworkTransportError` + +Error raised when an HTTP request never received a complete response (for example, connect/read timeouts, DNS failures, TLS handshake errors, decoding errors, redirect-loop exhaustion). Sibling of `UpstreamError` under `ToolExecutionError`. Unlike `UpstreamError`, `status_code` is always `None` because the client received no response. Classified by `ErrorKind` into one of: + +- `NETWORK_TRANSPORT_RUNTIME_TIMEOUT`—pool, connect, or read timeouts +- `NETWORK_TRANSPORT_RUNTIME_UNREACHABLE`—DNS, connection, TLS handshake, or remote-protocol failures +- `NETWORK_TRANSPORT_RUNTIME_UNMAPPED`—decode errors, redirect exhaustion, and other transport-level fallbacks + ### `ContextRequiredToolError` Error raised when a tool requires a context that was not provided.