Skip to main content

Troubleshooting

A rejected agent invocation carries a machine-readable code. Three different layers can raise one, and which layer it was tells you where to look:

  • Catalyst inbound auth runs in the sidecar, in front of your agent. It verifies the token the caller presented and exchanges it for a Catalyst-issued user token. Its codes are the ones a plain HTTP caller hits, because they are returned before any of your code runs.
  • The helper library runs inside your agent, once the request reaches it. Its codes only appear if you attached OAuthMiddleware — an agent without the middleware never returns an oauth.* code.
  • MCP identity enforcement runs on the way out, when your agent calls a server that requires a user identity. Its codes are returned to your agent on the MCP call, not to whoever invoked the agent.

Rejection codes​

CodeHTTP statusRaised byMeaning
malformed_jwt401Catalyst inbound authThe presented token is not a well-formed JWT, or is missing the iss claim.
invalid_grant401Catalyst inbound authThe token was refused — most often because its issuer is not federated.
temporarily_unavailable503Catalyst inbound authCatalyst could not verify or exchange the token right now. Retryable.
oauth.missing_token401Helper libraryNo X-Diagrid-User-Token header on the request.
oauth.invalid_signature401Helper libraryThe token's signature did not verify.
oauth.invalid_issuer401Helper libraryThe token's iss claim does not match the expected issuer.
oauth.invalid_audience401Helper libraryThe token's aud claim does not match the expected audience.
oauth.expired401Helper libraryThe token is past its expiry.
oauth.decode_error401Helper libraryThe token could not be decoded.
oauth.invalid_token401Helper libraryThe token failed validation for another reason.
oauth.missing_scope403Helper libraryThe user authenticated but lacks a required scope.
oauth.not_configured503Helper libraryThe middleware has no issuer to validate against.
oauth.verifier_unavailable503Helper libraryThe middleware has not yet loaded the issuer's signing keys. Retryable.
obo.exchange_failed502MCP identity enforcementCatalyst could not issue a delegated token for the outbound MCP call.

Catalyst inbound auth codes​

malformed_jwt​

What you sent in X-Diagrid-User-Token is not a parseable JWT, or it parses but carries no iss claim. Check that you sent the token itself and not a wrapper around it, that the Bearer prefix is present, and that nothing truncated the header in transit.

invalid_grant​

Catalyst verified the token's shape but refused it. The most common cause is an issuer that has not been federated: your token comes from a provider Catalyst does not trust for this deployment. Confirm a federation exists for the token's iss claim and is delivered to the region serving the project. This is the failure to expect first when using a custom identity provider token.

A token that is well-formed and federated but expired also lands here. For CLI callers, refresh credentials with diagrid login. For a custom identity provider token, re-acquire a fresh JWT from your provider's tooling.

temporarily_unavailable​

Catalyst could not reach the service that verifies and exchanges tokens. This is a transient platform condition, not a problem with your token — retry with backoff.

Helper library codes​

These are raised by OAuthMiddleware inside your agent and returned to the caller as the response body, {"error": "<code>"}. Read the rejected response to get the code — the middleware does not log a failed validation. It logs only when it cannot build a verifier at all, so diagrid agent logs <agent>, or diagrid app logs <app> if it runs as an app, is where you find oauth.not_configured and discovery warnings, not the codes above it.

Because Catalyst has already verified and exchanged the caller's token by the time the middleware runs, an oauth.* code usually points at the middleware's own configuration rather than at the caller.

oauth.missing_token​

The request reached your agent with no X-Diagrid-User-Token header. If the route is meant to be reachable without a user — a health or readiness probe, for example — set require_auth=False on the OAuthConfig so unauthenticated routes can share the app.

oauth.invalid_signature​

The signature on the presented JWT did not verify against the issuer's keys. This normally means the middleware resolved the wrong issuer. Leave issuer and jwks_uri unset so they are discovered from the sidecar, or confirm the values you set explicitly.

oauth.invalid_issuer and oauth.invalid_audience​

The token's iss or aud claim does not match what the middleware expects. As above, prefer discovery over setting issuer and audience by hand. If you must set them, they have to match what Catalyst mints — not your upstream identity provider, whose token is exchanged before it reaches you.

oauth.expired​

The token is past its expiry. Long-running invocations can outlive a short-lived token — check the gap between when the caller acquired the token and when the middleware saw it.

oauth.decode_error and oauth.invalid_token​

The token could not be decoded, or failed validation for a reason with no more specific code. The middleware does not surface the underlying parser message, so decode the token you sent and confirm it is the Catalyst-issued one your sidecar set on X-Diagrid-User-Token, rather than your upstream provider's token or another credential entirely.

oauth.missing_scope​

The user authenticated but the token does not carry a scope your policy requires. Confirm the scopes you declared in OAuthConfig(scopes={...}) and check that the identity provider issues them for this user — scope conventions per provider are covered in IdP federation.

oauth.not_configured and oauth.verifier_unavailable​

The middleware could not verify the token at all: it either resolved no issuer, or has not yet fetched the issuer's signing keys. Confirm the agent runs with a Catalyst sidecar, since that is where the middleware discovers its issuer. Discovery runs on the first authenticated request rather than at startup, so a misconfigured agent starts cleanly and fails only once traffic arrives. oauth.verifier_unavailable is retryable and typically clears once the key fetch succeeds.

MCP identity enforcement codes​

When an MCP server requires a user identity, the outbound call is checked before it is forwarded, and a rejected call never reaches the server. Your agent sees {"error": "<code>"} on the MCP call itself:

  • oauth.missing_token (401) — the invocation that reached your agent carried no user. A scheduled, pub/sub, or agent-to-agent trigger has none; route those callers through a server without enforcement.
  • oauth.missing_scope (403) — the user's token lacks a scope the server requires. Grant it in your identity provider, or re-run diagrid mcpserver access user-identity require with the scope dropped.
  • oauth.decode_error (401) — the user token could not be read. Confirm the caller forwards the token Catalyst issued, unmodified.
  • oauth.verifier_unavailable (503) — the identity could not be verified right now. Retryable.
  • obo.exchange_failed (502) — Catalyst could not issue the delegated token. This is not a configuration problem on your side. Retry, and open a support case if it persists.

These four oauth.* codes are the same strings the helper library returns. Which layer raised one is settled by where you saw it: on the response to the agent's invocation it came from the middleware, on the response to an MCP call it came from enforcement.

The tool ran, but saw no user​

The call succeeded and reached the MCP server, but the server reports no end user. Two causes, in the order worth checking:

  1. The server does not require a user identity. This is the default, and it is the common answer. A server that does not require one never receives one — Catalyst removes the user's token before forwarding. Turn the requirement on with diagrid mcpserver access user-identity require; there is no setting that forwards a user opportunistically when one happens to be present.

  2. The invocation carried no user in the first place. A scheduled, pub/sub, or cron trigger has no person behind it, so there is nothing to propagate. Confirm the agent was invoked with a user token, and that it builds its MCP client on the identity-aware HTTP client — a plain client sends no identity.

Once the requirement is on, this failure mode becomes visible instead of silent: a call with no user is rejected with oauth.missing_token rather than quietly running unattributed. That is the main reason to turn it on for any tool where attribution matters.