Error Codes
August 23, 2026 ยท View on GitHub
Every failure the cA2A runtime and verifier raise is a subclass of CA2AError. Each subclass carries a stable code string and an http_status. The code is what you match on in tests and callers. The HTTP status is what a service should return when the error crosses an A2A boundary. Both are defined in ca2a_runtime/errors.py and are the authoritative values below.
An error also carries a human-readable message and an optional detail. The message and detail are not stable and are for diagnostics only. Match on code, never on message text.
Registry
| Class | code | HTTP | When raised |
|---|---|---|---|
CA2AError | CA2A_ERROR | 500 | Base class for all cA2A runtime and verifier errors. Not raised directly; caught to handle any cA2A failure generically. |
ConfigError | CONFIG_ERROR | 500 | Ca2aConfig construction or verify_chain_file config load failed: unknown field, max_delegation_depth not a positive integer, missing config file, invalid YAML, or a non-mapping config root. |
InvalidCredential | INVALID_CREDENTIAL | 400 | A DelegationCredential is malformed or its Ed25519 signature does not verify: unsigned credential, bad signature, malformed fields, or a chain document that is not a list or {"chain": [...]}, a missing chain file, or invalid JSON. |
UntrustedDelegationRoot | UNTRUSTED_DELEGATION_ROOT | 403 | A chain is internally valid, but its root issuer is not pinned in the callee's trusted_root_issuers. Runtime authorization checks this before policy evaluation. |
ScopeEscalation | SCOPE_ESCALATION | 403 | A child grant claims authority its parent did not hold. Raised by verify_chain when a hop's scope is not a subset of its parent's scope. |
BrokenDelegationLink | BROKEN_DELEGATION_LINK | 409 | A hop does not chain to its stated parent, or continuity is broken: empty chain, a root credential that names a parent or has nonzero depth, a hop whose parent link or subject does not match the previous hop, or a hop depth that is not previous + 1. |
DelegationDepthExceeded | DELEGATION_DEPTH_EXCEEDED | 403 | A chain is longer than the configured max_delegation_depth. Raised by verify_chain. |
CredentialReplay | CREDENTIAL_REPLAY | 409 | A credential_id appears more than once in a single chain. Raised by verify_chain. |
CredentialNotYetValid | CREDENTIAL_NOT_YET_VALID | 403 | A hop's not_before bound is after the evaluation time. The chain is well formed and validly signed, but the grant is not yet in force. Raised by verify_chain. |
CredentialExpired | CREDENTIAL_EXPIRED | 403 | A hop's not_after bound is before the evaluation time. Raised by verify_chain. |
HolderProofInvalid | HOLDER_PROOF_INVALID | 401 | The presenter of a delegation chain did not prove it controls the leaf subject: no proof was presented, the proof was malformed, it answered a challenge this callee did not issue or which has expired, or its signature did not verify over the exact request being made. 401 rather than 403 because the chain may well carry the authority requested while the caller has not shown it is the party that authority was delegated to. Distinct from ATTESTATION_FAILED, which is about what the caller is running: a caller can appraise perfectly and still fail this. Raised by verify_holder_proof, handle_peer_request, and the A2A adapter on a malformed proof. See profile P-4a. |
AttestationUnsupported | ATTESTATION_UNSUPPORTED | 500 | An attestation provider was requested that the host cannot supply. Raised by any provider's attest when the host lacks what its collector needs, and by OpaqueProvider, which has no collector. The detail names the missing piece. See Peer Attestation. |
AttestationFailed | ATTESTATION_FAILED | 412 | Attestation evidence was present but did not verify. Raised by the SEV-SNP verifier on a malformed report, an untrusted or broken certificate chain, a bad report signature, or a measurement / report-data mismatch. See Peer Attestation. |
SealedChannelError | SEALED_CHANNEL_ERROR | 500 | The sealed peer channel could not construct or open a payload: an invalid peer public key, a malformed or unsupported sealed blob, a wrong key, or a tampered ciphertext (AEAD authentication failure). Fails closed; never returns unauthenticated plaintext. See Sealed Channel. |
ProvenanceLinkBroken | PROVENANCE_LINK_BROKEN | 409 | A DelegationRecord does not chain to its stated parent record, or a record was tampered with so its hash no longer matches a child's link: empty provenance chain, duplicate record_id, a root record that references a parent, a broken parent hash link, or a record whose credential_id or subject does not match the chain. Raised by verify_dag and cross_check_chain. |
ScopeNotPermitted | SCOPE_NOT_PERMITTED | 403 | A requested capability is not in the effective scope (the delegated leaf scope intersected with the callee's local policy). Raised by enforce_peer_call. |
TraceDigestUnsupported | TRACE_DIGEST_UNSUPPORTED | 501 | A delegation.parent_record_hash names a digest algorithm this verifier does not compute. The TRACE schema permits sha256: and sha384:; trace_record_hash computes LINK_DIGEST. Such a link is well formed and may be correct, so the chain is unverifiable at that hop rather than invalid, and is reported separately from PROVENANCE_LINK_BROKEN so an audit record does not carry a tampering finding that no tampering produced. Raised by verify_trace_dag, per hop. |
TransportError | TRANSPORT_ERROR | 400 | cA2A A2A-extension metadata was present but malformed or incomplete (missing delegation_chain, bad hop shape, non-base64url sealed_payload, etc.). Raised by ca2a_runtime.transport.parse_peer_request. Absence of all cA2A keys is not an error: that message is ordinary A2A input. |
Which errors are live today
ConfigError, InvalidCredential, ScopeEscalation, BrokenDelegationLink, DelegationDepthExceeded, CredentialReplay, CredentialNotYetValid, CredentialExpired, , ProvenanceLinkBroken, and TraceDigestUnsupported are raised by shipping code paths: attenuated delegation, offline chain verification, and the provenance DAG. ScopeNotPermitted is raised by the peer-call enforcement decision core (enforce_peer_call), and SealedChannelError by the sealed channel (SealedChannel.seal, open_sealed), both of which are implemented. TransportError is raised by the A2A metadata adapter when cA2A keys are present but cannot be parsed into a PeerRequest.
AttestationFailed is raised by the SEV-SNP verifier (chain, report signature, and measurement binding), and by a collector whose hardware returned evidence that does not commit the key and nonce it asked for. AttestationUnsupported is raised where a host cannot collect at all: no TPM or tpm2-pytss for tpm, no configfs-TSM or guest device for sev-snp and tdx, and on Azure confidential VMs, where SEV-SNP runs behind a paravisor that owns REPORT_DATA. See Peer Attestation and ROADMAP.md.
Handling errors
Catch the base class to handle any cA2A failure, or a specific subclass to react to one condition. The code attribute gives you the stable identifier and http_status the status to surface.
from ca2a_runtime.errors import CA2AError, ScopeEscalation
from ca2a_verify.verify import verify_chain_file
try:
verify_chain_file(
"chain.json", trusted_root_issuers={"<trusted-root-issuer-hex>"}
)
except ScopeEscalation as exc:
# A hop claimed more than its parent granted.
print(exc.code, exc.http_status) # SCOPE_ESCALATION 403
except CA2AError as exc:
# Any other cA2A failure.
print(exc.code, exc.http_status, exc.detail)
Verification fails closed. verify_chain, verify_dag, and cross_check_chain raise the first error they find rather than returning a partial result, so a caught CA2AError means the chain or DAG was rejected.
See also
- Delegation Chain for the checks behind
ScopeEscalation,BrokenDelegationLink,DelegationDepthExceeded,CredentialReplay,CredentialNotYetValid, andCredentialExpired. - Provenance DAG for the checks behind
ProvenanceLinkBroken. - Verification Library for
verify_chain,verify_chain_file,verify_dag, andcross_check_chain. - Failure Modes for how these errors map to observable runtime behavior.