Tool Execution Errors
Every TOOL_VENDOR_ code a governed extension tool can return, what caused it, and whether retrying helps.
When an extension tool fails, Scrydon returns a stable TOOL_VENDOR_* code
rather than the underlying error. This page lists every code, what causes it,
and what to do about it.
What a failure looks like
A failed tool call returns:
{
"success": false,
"output": {},
"error": "TOOL_VENDOR_EGRESS_DENIED",
"message": "The destination is not on the approved egress list for this connection.",
"metadata": { "retryable": false }
}In a workflow execution the same code appears against the node that failed:
{ "failedNodes": 1, "errors": ["b40d978e-…: TOOL_VENDOR_EGRESS_DENIED"] }Branch on error. message is a human-readable sentence for that code, meant
for whoever reads the failure — it is stable per code but may be reworded, so it
is not a branch key. It is selected by the code and never interpolates the
underlying reason, so it tells you nothing the code did not already.
The detail is withheld on purpose. Extension tools run inside a sandboxed microVM on your data, with credentials your organization owns. Raw vendor errors, credential material, network detail, and DLP findings never cross back to the caller, because the caller is often a model. You get a stable code; the full reason stays server-side.
Policy blocked the call
Governance refused the request. These are decisions, not faults — retrying an identical request always fails the same way.
| Code | Cause | What to do |
|---|---|---|
TOOL_VENDOR_NOT_SUPPORTED | The archive has no implementation for the hook the platform asked for (for example a model-discovery fetcher the vendor never declared) | Nothing to retry: the platform treats it as an empty answer. If a tool you expect to exist returns it, the archive does not export it |
TOOL_VENDOR_EGRESS_DENIED | The destination is not on the approved egress list for this connection | Add the host to the connection's allowed destinations |
TOOL_VENDOR_DESTINATION_DENIED | The URL the tool built is malformed, or its host is not allowed | Check the tool's inputs; then the connection's allowed hosts |
TOOL_VENDOR_REDIRECT_DENIED | The destination redirected somewhere policy does not allow | Allow the redirect target, or point the tool at the final URL |
TOOL_VENDOR_DLP_BLOCKED | Outbound DLP matched the request the tool actually emitted | Remove or reclassify the blocked data, or amend DLP policy |
TOOL_VENDOR_RESPONSE_SECRET_BLOCKED | The vendor's response contained credential-shaped data and was withheld | Expected when a vendor echoes secrets; investigate if unexpected |
TOOL_VENDOR_HEADER_DENIED | The tool set a forbidden or proxy-* header | Fix the tool; these headers are owned by the egress broker |
TOOL_VENDOR_CREDENTIAL_PLACEMENT_DENIED | A credential was placed somewhere its auth mode disallows — for example a header secret in a body | Correct the tool's auth-mode placement |
TOOL_VENDOR_UNKNOWN_CREDENTIAL_PLACEHOLDER | The request still held a scrydon_secret_* placeholder with nothing bound to it | Bind every placeholder the tool references, or remove it |
Limits and deadlines
| Code | Cause | Retry? |
|---|---|---|
TOOL_VENDOR_TIMEOUT | The invocation exceeded its deadline | Yes, if the operation is safe to repeat |
TOOL_VENDOR_EGRESS_TIMEOUT | A single outbound request exceeded its deadline | Yes, same caveat |
TOOL_VENDOR_EGRESS_BUDGET_EXHAUSTED | The tool made more outbound requests than its budget allows | No — reduce the tool's request count |
TOOL_VENDOR_REQUEST_TOO_LARGE | The outbound request exceeded the size cap | No — send less |
TOOL_VENDOR_RESULT_TOO_LARGE | The tool's result exceeded the size cap | No — return less, or paginate |
TOOL_VENDOR_CANCELLED | The parent execution was cancelled | No — start a new execution |
TOOL_VENDOR_EGRESS_CANCELLED | An outbound request was cancelled mid-flight | No |
A timeout does not tell you whether the request reached the destination. For anything with side effects, reconcile before retrying rather than assuming it did not commit.
The tool or its archive failed
| Code | Cause | What to do |
|---|---|---|
TOOL_VENDOR_EXECUTION_FAILED | The tool threw, or a failure with no more specific code | See Getting the real reason — this is the catch-all |
TOOL_VENDOR_PROTOCOL_INVALID | The archive's request, result, or log framing was malformed or out of bounds | Fix or update the code release |
TOOL_VENDOR_ARTIFACT_HASH_MISMATCH | Archive bytes did not match the digest pinned for this execution | Reinstall the extension; if it recurs, treat as tampering |
TOOL_VENDOR_INVOCATION_BINDING_MISMATCH | The invocation did not match the artifact it was bound to | Reinstall the extension and retry |
TOOL_VENDOR_ALREADY_INVOKED | A single-use sandbox received a second invocation | No action — the sandbox is single-use by design |
TOOL_VENDOR_UNAUTHORIZED | The sandbox rejected an unauthenticated invocation | Internal; report it if you see it |
TOOL_VENDOR_REQUEST_INVALID | The outbound request the tool built is malformed — an invalid body encoding, or a body on a GET/HEAD | Fix the tool's request construction |
TOOL_VENDOR_EGRESS_PROTOCOL_INVALID | The sandbox and the egress broker disagreed on framing | Internal; report it if you see it |
TOOL_VENDOR_CONTEXT_INVALID | Credentials could not be projected into the sandbox for this call | Recheck the connection's credentials and bindings |
TOOL_VENDOR_EXECUTION_FAILED is also what any unrecognized code collapses
to, so it is the code you are most likely to see and the least specific. It does
not by itself mean the tool's own logic failed.
The platform could not run it
These are infrastructure conditions. On Scrydon Cloud they are ours to fix; if you self-host, they point at your deployment.
| Code | Cause |
|---|---|
TOOL_VENDOR_ISOLATION_UNAVAILABLE | The sandbox could not be provisioned — no isolation capacity, or the pod could not be scheduled |
TOOL_VENDOR_ATTESTATION_REQUIRED | The vendor execution boundary has no current proof, so execution refuses rather than downgrading |
TOOL_VENDOR_EGRESS_UNAVAILABLE | The egress broker could not be attached to the sandbox |
TOOL_VENDOR_BROKER_NOT_READY | The tool called out before its egress grant was installed |
TOOL_VENDOR_EGRESS_FAILED | Outbound transport failed before a response |
TOOL_VENDOR_MANAGED_EXECUTION_UNAVAILABLE | The managed-process execution tier was unreachable |
TOOL_VENDOR_GRANT_UNAVAILABLE | The sandbox could not fetch its one-use grant |
TOOL_VENDOR_GRANT_INVALID | The grant it fetched did not validate |
TOOL_VENDOR_GRANT_TIMEOUT | The grant fetch exceeded its deadline |
TOOL_VENDOR_GRANT_EXPIRED | The grant's own deadline passed before the sandbox redeemed it |
TOOL_VENDOR_GRANT_INSTALL_FAILED | The grant install failed for a reason outside the four above |
TOOL_VENDOR_GRANT_STORE_UNAVAILABLE | The grant store was unreachable |
TOOL_VENDOR_DLP_POLICY_UNAVAILABLE | DLP policy could not be loaded, so the call failed closed |
TOOL_VENDOR_EGRESS_POLICY_UNAVAILABLE | Egress policy could not be loaded, so the call failed closed |
TOOL_VENDOR_CLEANUP_FAILED | The sandbox or its grants could not be proven torn down |
The two *_POLICY_UNAVAILABLE codes are deliberate: when governance policy
cannot be loaded, the call is refused rather than run ungoverned.
When to retry
Do not infer retryability from the code. Read metadata.retryable — it is set
from the failure's transport characteristics, so the same code can be retryable
in one case and not in another.
As a rule:
- Never retry a policy denial. The decision is deterministic.
- Retry with backoff on capacity and transport conditions, and only when the operation is safe to repeat.
- Never auto-retry a side-effecting call after a timeout without reconciling first — a timeout does not prove the request did not commit.
- Wait at least
retryAfterseconds when the failure carries one. It is the provider's own requested back-off, and a retry that lands sooner meets the same limit.
In your extension, report a non-OK provider response with
throwExtensionHttpError(response) from
@scrydon/sdk-authoring/extensions/extension-failure rather than a bare
Error. It carries the HTTP status and the provider's error code and, when the
provider sent a retry-after-ms or retry-after header, its back-off as
retryAfter in whole seconds. The platform forwards that value as the error's
retryAfter and the response's Retry-After header — a bare Error loses all
three, so every caller can only guess.
What the platform already did before answering you
The platform applies that last rule to itself, so the failure you receive is not the first thing that went wrong — but for a tool call, it is the only thing.
Each platform operation declares whether it is safe to repeat. For a read-only one, a transient provider failure is retried internally (bounded, with backoff, against the same provider — a retry is never permission to switch vendors), so a failure that reaches you has already survived those attempts.
Tool execution is not read-only. A governed tool call can file a ticket, post a message, or create a record, so the platform makes exactly one attempt and hands you the failure. The same applies to creating, renewing, or deleting a webhook subscription. Two consequences:
- A
TOOL_VENDOR_TIMEOUTreaching you means one attempt timed out — not that three did. The side effect may still have committed. - If you retry, yours is the second attempt, not the fifth. Budget accordingly, and reconcile first.
Supplying an idempotencyKey does not change this. The platform forwards it to
the extension as an ordinary input field; whether the vendor deduplicates on it
is the vendor's behaviour, not a platform guarantee, so the platform does not
retry on the strength of one.
Getting the real reason
If you self-host, the untruncated reason is server-side. API Platform logs it
against Governed vendor tool execution failed:
{
"code": "TOOL_VENDOR_EXECUTION_FAILED",
"executionId": "platform-parent-…",
"childExecId": "exec-…",
"executionTier": "microvm",
"planeReason": "…"
}planeReason is the failure text, bounded to 512 characters. Note that
"The operation timed out." there means a client-side budget expired, not that
the platform decided anything — usually the sandbox could not be provisioned in
time.
On Scrydon Cloud, quote the executionId to support.