Skip to content

fix(mcp): stop OAuth-broken connections from reporting Connected forever - #1841

Merged
Aaronontheweb merged 1 commit into
netclaw-dev:devfrom
nixie-ai:fix/mcp-oauth-refresh-stuck-status
Aug 10, 2026
Merged

fix(mcp): stop OAuth-broken connections from reporting Connected forever#1841
Aaronontheweb merged 1 commit into
netclaw-dev:devfrom
nixie-ai:fix/mcp-oauth-refresh-stuck-status

Conversation

@nixie-ai

@nixie-ai nixie-ai commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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 SDK AuthorizationCallbackHandler is 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 throws McpOAuthAuthorizationInProgressException forever. 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

  1. 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 answers null — 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.

  2. MarkAwaitingAuthorization: an auth-class failure during catalog refresh (IsAuthFailure, now also covering McpOAuthAuthorizationInProgressException) demotes the server to AwaitingAuth. The catalog stays visible (wiping it would hide which server needs reauthorization), the refresh loop stops (it only runs for Connected), and an operational alert names netclaw 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.

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 Aaronontheweb added mcp Model context protocol server / client issues. bug Something isn't working labels Aug 10, 2026

@Aaronontheweb Aaronontheweb left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@Aaronontheweb
Aaronontheweb merged commit 492a4e5 into netclaw-dev:dev Aug 10, 2026
15 checks passed
@Aaronontheweb Aaronontheweb mentioned this pull request Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working mcp Model context protocol server / client issues.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants