CharterError, so a host
application can catch the whole surface with one except. The runtime decides
nothing about what happens next: it raises a typed exception and hands the
decision back.
CharterError
Optional[str]
A documentation slug, such as
"auth/oauth-flow", or None. Set on errors
whose fix is a procedure rather than an edit: registering an OAuth app,
choosing scopes, standing up a callback route. Errors whose message already
states the fix carry none, and neither does ToolValidationError, whose
message is written to be handed back to a model that cannot follow a link.Optional[str]
The slug rendered against the documentation site, or
None. Appended to the
message by str(exc), and readable on its own for a host that formats errors
itself. Appending .md to it returns the page as markdown, which is what an
agent debugging a pack should fetch.tests/test_error_docs.py can resolve
every slug the library can print against the pages that ship beside it. A link
inside an exception reaches someone who is already stuck; a dead one is worse
than none.
DeclarationError
Path(), Query() or Body() marker, a Pagination naming
half a style, an Envelope that could never detect a failure, a Format
naming a transform nobody registered.
It names whose mistake this is, not when it surfaced. Most are raised while a
tool is built or a declaration validated. Some cannot be detected until a request
is assembled, and come out of ainvoke instead — an unmarked field, a body that
will not form-encode, a pack whose host was never configured. Either way the fix
is in the declaration and the reader is its author, which is why these carry a
docs link where an error written for a model does not.
Also a ValueError, deliberately. Several are raised inside a pydantic
validator, which converts ValueError and nothing else, and a host that already
catches ValueError around a declaration keeps working unchanged. except CharterError catches them too.
CredentialError
str
What went wrong.
Optional[str]
Whose credentials failed —
"google" — or None when the failure is not
attributable to one provider.Optional[int]
The HTTP status that triggered it, when it came from a response —
401 by
default, or whatever credential_statuses declared. None when the
credentials were missing locally or a refresh failed.str(exc) prefixes the provider in brackets when there is one.
Raised by: a provider with nothing to hand back (EnvTokenProvider on an unset
variable, an unconfigured pack, a SubjectProvider with no subject set); a
token endpoint refusing a refresh or an exchange; a response whose status is in
credential_statuses; an envelope failure whose code is in
credential_errors; and the constructor guards on StaticTokenProvider,
CallbackProvider, OAuth2Client and OAuth2Flow.
The library never re-authenticates on its own. This is the signal to run
whatever flow you own and retry — see
the OAuth flow.
ToolValidationError
str
Written to be handed straight back to a model as the tool result: it names the
offending fields and how to fix them.
Optional[str]
Which tool’s input failed, when known.
list[dict[str, Any]]
The structured per-field errors.
[] when none were supplied — never None,
so iterating is always safe.ainvoke validating arguments against
llm_schema(). A call that fails here cost no request, no rate-limit budget and
no money.
See handling it for the retry loop
this is designed for, and
the auto-corrections applied before it
raises.
TransformError
str
What failed.
Optional[str]
The field it was applied to, when known. Appended to
str(exc).apply_transform when the name is not
registered (the message lists what is), when the value does not validate as the
transform’s semantic type, or when the transform function itself raises. Also
before the network.
APIError
int
The status the API returned.
200 when a declared
envelope found the failure inside a
success — that really was the status.str
An excerpt of the response body, truncated to 500 characters so it stays safe
to pass into a context window.
Optional[str]
The request URL, when known.
Optional[int]
Seconds the server asked the caller to wait, parsed from
Retry-After —
usual on 429, common on 503. Only the delta-seconds form is parsed; the
HTTP-date form is legal but rare in JSON APIs, and guessing at clock skew is
worse than reporting nothing.Two fallbacks, for APIs that answer the same question elsewhere. When there is
no Retry-After, X-RateLimit-Reset is read — but only when
X-RateLimit-Remaining is 0, so an ordinary response carrying its quota
headers is not reported as a wait. GitHub needs this: it sends no
Retry-After and answers 403 rather than 429. And an
Envelope with a retry_after
resolver fills this in for an API that refuses inside a 200, which is how a
cost-budgeted API like Shopify reports a throttle.str(exc) joins the status, URL, message, retry hint and body excerpt with em
dashes.
Charter never retries, and retry_after is why: it refuses to discard the
server’s own answer to “when?”, and leaves the policy to you.
Catching the surface
CharterError first would swallow them all.
DeclarationError is not in that block, and the trailing except CharterError
is what catches it: most of them are raised while a tool is built, but the ones
that need a request to be detected arrive here instead. Because it is also a
ValueError, code that already guards a declaration with except ValueError
keeps catching the build-time ones.
Related
- Handling a validation error
- Envelopes — the failures that arrive inside a 200
- Limitations — what the runtime declines to decide for you