[BUG] Slack MCP (HTTP OAuth): tokens cleared on every 401 instead of using refresh token — forces full re-auth ~each session

Resolved 💬 3 comments Opened May 14, 2026 by zofiasp Closed May 18, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code (bug observed on 2.1.141; temporarily downgraded to 2.1.140 as a workaround — see below)

What's Wrong?

The HTTP-OAuth-based Slack MCP server (https://mcp.slack.com/mcp, configured via /mcp) requires a full interactive OAuth re-authentication every time a 401 Unauthorized is received from the server — including transient cases like a dropped HTTP transport — even though a valid refresh token is present in the on-disk credential store.

On any 401, the CLI immediately logs Cleared stored tokens, opens a new authorisation URL in the browser, and prompts re-authentication. The refresh token in ~/.claude/.credentials.json is destroyed before any refresh attempt is made. A grant_type=refresh_token exchange is never attempted, even though the SDK logs Has refresh token: true repeatedly on the preceding successful tool calls.

In practice this means re-authenticating Slack approximately once per work-day (Slack issues access tokens with expires_in: 43200 / 12 h), and — more disruptively — it breaks any headless claude -p workflow that depends on the Slack MCP, because such workflows cannot complete the interactive OAuth flow.

What Should Happen?

On a 401 from an MCP HTTP server, the CLI should:

  1. If a refreshToken is present in storage for that server, attempt a grant_type=refresh_token exchange first.
  2. Persist the new access/refresh token pair on success and retry the original tool call.
  3. Only clear stored tokens and fall back to the full authorisation-code flow if the refresh exchange itself fails (e.g. 400/401 from the token endpoint).

A debug log line on the refresh attempt path would also help diagnose this from mcp-logs-slack/.

Error Messages/Logs

The relevant transition during a single session that had previously authed successfully (extracted from ~/Library/Caches/claude-cli-nodejs/-<cwd>/mcp-logs-slack/):

2026-05-13T13:30:37.918Z  Channel notifications skipped: server did not declare claude/channel capability
2026-05-13T13:31:07.872Z  Calling MCP tool: slack_search_users
2026-05-13T13:31:07.872Z  No token data found
2026-05-13T13:31:08.390Z  Saving discovery state (authServer: https://mcp.slack.com)
2026-05-13T13:31:08.468Z  No access token in storage
2026-05-13T13:31:08.469Z  HTTP connection dropped after 2760s uptime
2026-05-13T13:31:08.469Z  Connection error: Unauthorized
2026-05-13T13:31:08.469Z  Tool 'slack_search_users' failed after 0s: Unauthorized
2026-05-13T13:31:08.469Z  Tool call returned 401 Unauthorized - token may have expired
2026-05-13T13:31:18.834Z  No access token in storage
2026-05-13T13:31:24.846Z  Cleared stored tokens
2026-05-13T13:31:24.846Z  Using redirect port: <PORT> (from config)
2026-05-13T13:31:25.086Z  Fetched OAuth metadata with scope: …
2026-05-13T13:31:25.421Z  Authorization URL: https://slack.com/oauth/v2_user/authorize?…
2026-05-13T13:31:25.421Z  Opening authorization URL: …

The sequence:

  1. HTTP connection drops after 46 min of uptime (well within the 12 h token life).
  2. Server returns 401 on the next tool call.
  3. CLI logs Cleared stored tokens, throwing away the still-valid refresh token.
  4. CLI generates a fresh code verifier and opens the full OAuth URL in the browser.

After a successful re-auth on 2026-05-14T10:38, a fresh expires_in: 43200 token was issued and has_refresh_token: true was reported again — Slack is issuing refresh tokens consistently; the CLI just isn't using them on 401.

Steps to Reproduce

  1. Configure the Slack MCP via /mcp and complete the OAuth flow.
  2. Use the Slack MCP normally for a while so the access token is cached in ~/.claude/.credentials.json (the entry sits under mcpOAuth.slack|<hash> and includes a valid accessToken, refreshToken, and expiresAt).
  3. Either (a) leave the session idle long enough for the HTTP transport to drop, or (b) wait for the access token to expire (12 h).
  4. Make any Slack MCP tool call (e.g. slack_search_users, slack_read_channel).

Expected: the CLI exchanges the refresh token for a new access token silently and the tool call succeeds.

Actual: the CLI clears the tokens, opens the authorisation URL in the browser, and fails the tool call with Unauthorized. Headless claude -p invocations fail outright because no human is present to complete the interactive flow.

Claude Model

Not sure / Multiple models (this is a CLI/MCP behaviour, model-independent)

Is this a regression?

Yes, this worked in a previous version

Last Working Version

2.1.140 (anecdotal — the symptom appeared after updating to 2.1.141)

Claude Code Version

2.1.141 (Claude Code) — bug observed here. Currently running 2.1.140 (Claude Code) as a temporary workaround.

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

Workaround applied locally: pinned the CLI to 2.1.140 (the install is versioned at ~/.local/share/claude/versions/):

ln -sfn ~/.local/share/claude/versions/2.1.140 ~/.local/bin/claude

This is a temporary mitigation, not a fix — I haven't confirmed exactly where the regression was introduced.

Other context:

  • Other HTTP-OAuth MCPs configured under mcpOAuth in ~/.claude/.credentials.json appear to be working fine — but those are used more intermittently than Slack and the symptom may not have surfaced yet.
  • Sanitised log excerpts can be supplied on request from ~/Library/Caches/claude-cli-nodejs/-<cwd>/mcp-logs-slack/.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗