Scrydon
ExtensionsAuthoring

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.

CodeCauseWhat to do
TOOL_VENDOR_NOT_SUPPORTEDThe 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_DENIEDThe destination is not on the approved egress list for this connectionAdd the host to the connection's allowed destinations
TOOL_VENDOR_DESTINATION_DENIEDThe URL the tool built is malformed, or its host is not allowedCheck the tool's inputs; then the connection's allowed hosts
TOOL_VENDOR_REDIRECT_DENIEDThe destination redirected somewhere policy does not allowAllow the redirect target, or point the tool at the final URL
TOOL_VENDOR_DLP_BLOCKEDOutbound DLP matched the request the tool actually emittedRemove or reclassify the blocked data, or amend DLP policy
TOOL_VENDOR_RESPONSE_SECRET_BLOCKEDThe vendor's response contained credential-shaped data and was withheldExpected when a vendor echoes secrets; investigate if unexpected
TOOL_VENDOR_HEADER_DENIEDThe tool set a forbidden or proxy-* headerFix the tool; these headers are owned by the egress broker
TOOL_VENDOR_CREDENTIAL_PLACEMENT_DENIEDA credential was placed somewhere its auth mode disallows — for example a header secret in a bodyCorrect the tool's auth-mode placement
TOOL_VENDOR_UNKNOWN_CREDENTIAL_PLACEHOLDERThe request still held a scrydon_secret_* placeholder with nothing bound to itBind every placeholder the tool references, or remove it

Limits and deadlines

CodeCauseRetry?
TOOL_VENDOR_TIMEOUTThe invocation exceeded its deadlineYes, if the operation is safe to repeat
TOOL_VENDOR_EGRESS_TIMEOUTA single outbound request exceeded its deadlineYes, same caveat
TOOL_VENDOR_EGRESS_BUDGET_EXHAUSTEDThe tool made more outbound requests than its budget allowsNo — reduce the tool's request count
TOOL_VENDOR_REQUEST_TOO_LARGEThe outbound request exceeded the size capNo — send less
TOOL_VENDOR_RESULT_TOO_LARGEThe tool's result exceeded the size capNo — return less, or paginate
TOOL_VENDOR_CANCELLEDThe parent execution was cancelledNo — start a new execution
TOOL_VENDOR_EGRESS_CANCELLEDAn outbound request was cancelled mid-flightNo

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

CodeCauseWhat to do
TOOL_VENDOR_EXECUTION_FAILEDThe tool threw, or a failure with no more specific codeSee Getting the real reason — this is the catch-all
TOOL_VENDOR_PROTOCOL_INVALIDThe archive's request, result, or log framing was malformed or out of boundsFix or update the code release
TOOL_VENDOR_ARTIFACT_HASH_MISMATCHArchive bytes did not match the digest pinned for this executionReinstall the extension; if it recurs, treat as tampering
TOOL_VENDOR_INVOCATION_BINDING_MISMATCHThe invocation did not match the artifact it was bound toReinstall the extension and retry
TOOL_VENDOR_ALREADY_INVOKEDA single-use sandbox received a second invocationNo action — the sandbox is single-use by design
TOOL_VENDOR_UNAUTHORIZEDThe sandbox rejected an unauthenticated invocationInternal; report it if you see it
TOOL_VENDOR_REQUEST_INVALIDThe outbound request the tool built is malformed — an invalid body encoding, or a body on a GET/HEADFix the tool's request construction
TOOL_VENDOR_EGRESS_PROTOCOL_INVALIDThe sandbox and the egress broker disagreed on framingInternal; report it if you see it
TOOL_VENDOR_CONTEXT_INVALIDCredentials could not be projected into the sandbox for this callRecheck 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.

CodeCause
TOOL_VENDOR_ISOLATION_UNAVAILABLEThe sandbox could not be provisioned — no isolation capacity, or the pod could not be scheduled
TOOL_VENDOR_ATTESTATION_REQUIREDThe vendor execution boundary has no current proof, so execution refuses rather than downgrading
TOOL_VENDOR_EGRESS_UNAVAILABLEThe egress broker could not be attached to the sandbox
TOOL_VENDOR_BROKER_NOT_READYThe tool called out before its egress grant was installed
TOOL_VENDOR_EGRESS_FAILEDOutbound transport failed before a response
TOOL_VENDOR_MANAGED_EXECUTION_UNAVAILABLEThe managed-process execution tier was unreachable
TOOL_VENDOR_GRANT_UNAVAILABLEThe sandbox could not fetch its one-use grant
TOOL_VENDOR_GRANT_INVALIDThe grant it fetched did not validate
TOOL_VENDOR_GRANT_TIMEOUTThe grant fetch exceeded its deadline
TOOL_VENDOR_GRANT_EXPIREDThe grant's own deadline passed before the sandbox redeemed it
TOOL_VENDOR_GRANT_INSTALL_FAILEDThe grant install failed for a reason outside the four above
TOOL_VENDOR_GRANT_STORE_UNAVAILABLEThe grant store was unreachable
TOOL_VENDOR_DLP_POLICY_UNAVAILABLEDLP policy could not be loaded, so the call failed closed
TOOL_VENDOR_EGRESS_POLICY_UNAVAILABLEEgress policy could not be loaded, so the call failed closed
TOOL_VENDOR_CLEANUP_FAILEDThe 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 retryAfter seconds 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_TIMEOUT reaching 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.

On this page

On this page