fix(mcp): stop OAuth-broken connections from reporting Connected forever - #1841
Merged
Aaronontheweb merged 1 commit intoAug 10, 2026
Merged
Conversation
An MCP server whose OAuth tokens die mid-life used to wedge the daemon into a permanent lie: 1. Interactive authorization publishes a client whose SDK callback delegate is the one-shot flow handler. Hours later, when the access token expires, the SDK re-invokes that consumed delegate and every subsequent request throws "OAuth authorization is already in progress" forever. The published client now gets a delegate that forwards only while its flow is pending; a consumed flow answers null, producing the clean "null authorization result" failure instead of an infinite in-progress loop. 2. Catalog refreshes on Connected servers classified that failure as transient, so status kept reporting "Connected (31 tools)" and the 30s refresh loop retried the dead token indefinitely. An auth-class refresh failure now demotes the server to AwaitingAuth (catalog stays visible), stops the refresh loop, and emits an operator alert naming "netclaw mcp auth <name>" as the remedy. Observed in production 2026-08-08/09: two consecutive nightly Jira PR review runs blocked by exactly this failure mode.
Aaronontheweb
approved these changes
Aug 10, 2026
Aaronontheweb
left a comment
Collaborator
There was a problem hiding this comment.
LGTM - so the issue was we weren't providing the right signaling back to the MCP SDK when OAuth tokens expired? That's subtle - glad you caught it
Merged
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
An OAuth-protected MCP server whose tokens die mid-life wedges the daemon into a permanent false-healthy state. Observed in production on two consecutive nights (2026-08-08 and 2026-08-09), where it blocked both nightly unattended Jira PR review runs.
Two distinct failure modes, one shared root:
Mode A — "already in progress" forever. Interactive authorization (
netclaw mcp auth) publishes a client whose SDKAuthorizationCallbackHandleris the one-shot flow's delegate. Hours later, when the access token expires (~7-8h for the Atlassian connector), the SDK's 401 recovery re-invokes that consumed delegate. Its ownership latch rejects re-entry, so every request throwsMcpOAuthAuthorizationInProgressExceptionforever. No amount of retry or reconnect recovers — the poisoned delegate is baked into the published client for its whole life.Mode B — status lies. Both modes surface during catalog refresh on a Connected server, where the failure was classified as transient: status kept reporting
Connected (N tools)while every real call failed, and the 30s refresh loop retried the dead token indefinitely. Operators and monitoring had no signal that reauthorization was needed.Fix
CreateAuthorizationCallbackHandler: flow-built clients get a delegate that forwards to the flow only while it is pending. Once the flow is terminal (completed, failed, or expired) the delegate answersnull— the SDK then fails with the clean, classifiable "null authorization result" error instead of looping on "already in progress" forever. Background (flow-less) clients keep the existing null-returning delegate unchanged.MarkAwaitingAuthorization: an auth-class failure during catalog refresh (IsAuthFailure, now also coveringMcpOAuthAuthorizationInProgressException) demotes the server toAwaitingAuth. The catalog stays visible (wiping it would hide which server needs reauthorization), the refresh loop stops (it only runs forConnected), and an operational alert namesnetclaw mcp auth <name>as the remedy.Tests
AuthorizationFailureDuringRefresh_MarksAwaitingAuthAndStopsTheRefreshLoop— production Mode-B scenario: auth failure during refresh demotes status, keeps the catalog visible, and stops the retry loop.StuckAuthorizationFlowDuringRefresh_MarksAwaitingAuth— production Mode-A scenario.TerminalFlowCallbackHandler_ReturnsNullInsteadOfAlreadyInProgress/PendingFlowCallbackHandler_ForwardsToTheFlow— delegate behavior on both sides of the flow lifecycle.Full MCP test suite: 160/160 passing. Slopwatch: 0 issues.