New message during a still-pending MCP authorization hits opaque 'has a live external authorization' error
@jhrozek is already working on this.
Since Sep 10, 2026.
- Dominant language
- Go
- Stars
- 152
- Forks
- 16
- Avg merge
- 14h 48m
- Merged PRs (30d)
- 536
Description
Summary
A user sends a message that triggers a protected MCP tool call (broker/DCR mode). The tool call parks the session for authorization (StateAuthorizing), the user is shown the consent flow. If the user sends a new message before that authorization finishes resolving, the request fails with an opaque error instead of a clear "authorization still pending" response:
stream error: rpc error: code = FailedPrecondition desc = server: failed precondition: session "0aa385b9d1606022f4a60c29eb6ebc41" has a live external authorization
Reproduction (observed live)
Session 0aa385b9d1606022f4a60c29eb6ebc41, against a live mecak8s deployment with connector_gateway configured (client.mode: dcr):
- User: "list my meeting notes in read.ai"
- Model calls
connector_gateway'sRead_ai_f1bde01f_list_meetingstool - this is the FIRST use of the Read.ai backend for this user, so it parks the session for authorization (expected, matches ADR 0310/0311's lazy-authorization design). - User sends a follow-up message ("can you share") before completing/confirming the authorization has resolved.
- Server returns
FailedPrecondition: session ... has a live external authorization- a hard, opaque error with no guidance.
Root cause
Traced end to end:
- Every run-entry (new message) passes through
reopenLoadedSession(internal/adapter/server/service.go:4042) ->interruptRestoredAuthorizationLocked(internal/adapter/server/mcp_authorization.go:937) whenever the session's persisted state isStateAuthorizing. interruptRestoredAuthorizationLocked's own doc comment says it "repairs authorizing state whose exact process-local transaction is unavailable" - i.e. it is meant for the case where the process that originally parked the session is gone (crash, restart, redeploy).- Its ONLY signal for reaching that conclusion is
sess.State == StateAuthorizing, loaded fresh from the durable store. It cannot distinguish "orphaned by a dead process" from "still legitimately in-flight, same live process, user just sent a message before the authorization resolved" - both look identical from this check alone. - When it finds the authorization still live via
attachment.AuthorizationStatus, it correctly refuses to silently clear it (a good safety property for the orphan case) but returns this hardFailedPreconditionfor BOTH cases, with no differentiation and no actionable message for the still-in-flight case.
There does not appear to be any earlier check in the run-entry funnel that recognizes "this session has a pending, still-resolving authorization, please wait or cancel" and short-circuits to a friendly response before falling into the orphan-recovery path.
Suggested fix direction
Distinguish the two cases before returning this error - for example, check whether the parking transaction is still genuinely being tracked by the SAME process/owner (not just whether the external authorization is still live) before concluding the session needs "restoring" at all. If it's still owned by this same live process, return a clear, distinct "authorization pending, please complete or cancel it" response instead of routing into the dead-process repair path and its opaque error. CancelMCPAuthorization already exists as a working escape hatch and does not hit this guard - the gap is that a user has no way to know from this error message that cancelling (or just waiting) is the way forward.
Impact
Any user of an oauth-mode MCP broker backend (GitHub, connector-gateway, etc.) who sends a follow-up message while their first authorization is still resolving hits this same confusing dead end. Likely also explains an earlier, separately-reported symptom from the same day ("took very long to confirm the connection, had to press Enter repeatedly, then a confusing error about state") on the same deployment/feature.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.