[BUG] Cowork agentic mode: plugin OAuth helper loses flow state between turns — authorization codes issued but never exchanged ("No OAuth flow is in progress")

Open 💬 0 comments Opened Jun 12, 2026 by vsnoothi

Preflight Checklist

  • [x] I have searched existing issues and this consolidates several (see "Related issues" below — the canonical one, #49043, was closed by the stale-bot without a fix)
  • [x] This is a single bug report (one root cause: plugin OAuth helper state is not durable across Cowork agentic turns)
  • [x] Reproduced on the latest Cowork desktop build (2026-06-11)

What's Wrong?

We operate a remote MCP server (https://mcp.adspirer.com/mcp — OAuth 2.1 authorization server with Dynamic Client Registration, PKCE, hosted https://claude.ai/api/mcp/auth_callback and RFC 8252 loopback support). Our marketplace plugin (adspirer-ads-agent) ships an .mcp.json pointing at that URL.

In Cowork agentic mode, the plugin OAuth flow can never complete. Sequence observed (verbatim from an affected user, reproduced by us):

  1. mcp__plugin_adspirer-ads-agent_adspirer__authenticate returns a valid authorize URL (loopback redirect_uri = http://localhost:<port>/callback, PKCE S256).
  2. User authorizes in the browser within seconds. Our server issues the authorization code and 302s to the loopback.
  3. The loopback listener is already gone (torn down between agentic turns), so the user copies the callback URL and pastes it into complete_authentication.
  4. complete_authentication returns "No OAuth flow is in progress." — even though the state in the pasted URL exactly matches step 1.

The client-side OAuth helper keeps the pending-flow record (including the PKCE code_verifier) in process memory, and Cowork disconnects/reconnects MCP between tool calls in agentic mode, wiping it. The server never receives a /oauth/token call at all.

Server-side forensic evidence (authorization-server logs/DB, one user, same hour, 2026-06-11 UTC)

| Attempt | Client | redirect_uri | Code issued | Code exchanged at /oauth/token |
|---|---|---|---|---|
| 22:14:20 | cli-claude-code-* (plugin) | http://localhost:51019/callback | yes (state + PKCE persisted) | never |
| 22:14:32 | cli-claude-code-* (plugin) | http://localhost:51019/callback | yes | never |
| 22:15:23 | cli-claude-code-* (plugin) | http://localhost:60222/callback | yes | never |
| 22:16:35 | cli-claude-code-* (plugin) | http://localhost:57572/callback | yes | never |
| 22:20:07 | claude (custom connector) | https://claude.ai/api/mcp/auth_callback | yes | 0.4 seconds later → working token |
| 22:38 / 22:42 | cli-claude-code-* (plugin) | http://localhost:<port>/callback | yes | never |

Same user, same server, same hour: every hosted-callback flow completes instantly; every plugin/loopback flow dies client-side after our redirect. Across our production traffic the pattern is consistent: hundreds of successful loopback exchanges from Claude Code CLI (whose process persists), ~0% success from Cowork agentic sessions.

Why the server cannot mitigate this

  • Our flow state is fully DB-backed (code, state, PKCE challenge) — durable across anything on our side. The lost state (the code_verifier) exists only inside the Cowork helper; we never see it by design.
  • We cannot ship our own authenticate/complete_authentication MCP tools: the MCP transport is correctly 401-gated, so an auth-establishing tool sits behind the auth it would create.
  • OOB / paste-a-code (urn:ietf:wg:oauth:2.0:oob) is not supported by Claude clients (per the connector docs: hosted callback and loopback only; "user-pasted bearer tokens are not yet supported"), so there is no manual recovery path we can offer.
  • The loopback listener lifecycle is owned by the client; we cannot keep it alive between turns.

Related issues (same root cause family, multiple vendors)

  • #49043 — Asana MCP, identical symptom; closed by stale-bot as NOT_PLANNED
  • #60290 — Atlassian MCP, identical symptom; auto-closed as duplicate of #49043
  • #59725 — Slack MCP (open)
  • #61262 — Greptile plugin: OAuth completes but tokens not persisted
  • #51398 — ${CLAUDE_PLUGIN_DATA} is session-scoped in Cowork, so even disk-caching plugin tokens there doesn't survive a new conversation
  • #52565, #58607, #34832 — token/registration persistence across restarts and compaction

The ecosystem is currently routing around the client: Atlassian's official desktop docs tell users to connect through the mcp-remote npm bridge; Asana's docs steer users to custom connectors and label mcp-remote experimental. We now do the same (our consent page detects loopback flows and pre-emptively explains the failure with the custom-connector workaround) — but every remote-MCP vendor is independently rediscovering and working around this.

What Should Happen?

Any one of these fixes the class of bug:

  1. Persist the plugin OAuth helper's pending-flow state (state param → code_verifier, keyed on state) durably across agentic turns / MCP reconnects, so complete_authentication can finish a flow started in an earlier turn.
  2. Keep (or re-bind) the loopback listener across message turns so the redirect can be received without manual paste-back.
  3. Route plugin-bundled remote MCP OAuth through the hosted claude.ai/api/mcp/auth_callback path — the custom-connector flow already proves it works in Cowork.

We're happy to share anonymized server logs, DB traces, or a test account against https://mcp.adspirer.com/mcp. Our endpoints are standard OAuth 2.1 and pass connector validation.

Environment

  • Cowork desktop (macOS), agentic mode, marketplace plugin with .mcp.json → remote HTTP MCP server
  • Server: OAuth 2.1 + DCR + PKCE (S256), hosted + loopback redirect support

View original on GitHub ↗