[BUG] MCP OAuth: "Token expired without refresh token" despite refresh token being stored in Keychain

Resolved 💬 3 comments Opened Feb 12, 2026 by ilyakorolev Closed Feb 16, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Claude Code fails to use stored OAuth refresh tokens for HTTP MCP servers. The refresh token is correctly obtained during OAuth flow and persisted to macOS Keychain, but on the next session startup, Claude Code reports "Token expired without refresh token" and forces a full re-authentication.

This means users must manually re-authenticate via /mcp every ~24 hours (whenever the access token expires).

What Should Happen?

When an access token expires, Claude Code should read the stored refresh token from Keychain and use it to obtain a new access token silently, without user intervention.

Error Messages/Logs

Session startup (access token expired):

[DEBUG] MCP server "plugin:circleback:circleback": HTTP transport options: {"url":"https://app.circleback.ai/api/mcp","headers":{"User-Agent":"claude-code/2.1.39 (cli)"},"hasAuthProvider":true,"timeoutMs":60000}
[DEBUG] MCP server "plugin:circleback:circleback": Token expired without refresh token
[DEBUG] MCP server "plugin:circleback:circleback": Token expired without refresh token
[DEBUG] MCP server "plugin:circleback:circleback": Generated new OAuth state
[DEBUG] MCP server "plugin:circleback:circleback": HTTP Connection failed after 953ms: Unauthorized (code: none, errno: none)
[ERROR] MCP server "plugin:circleback:circleback" Error: Unauthorized
[DEBUG] MCP server "plugin:circleback:circleback": Authentication required for HTTP server

After manual re-auth (tokens are saved correctly):

[DEBUG] MCP server "plugin:circleback:circleback": Saving tokens
[DEBUG] MCP server "plugin:circleback:circleback": Token expires in: 86399
[DEBUG] MCP server "plugin:circleback:circleback": Has refresh token: true
[DEBUG] MCP server "plugin:circleback:circleback": Auth result: AUTHORIZED

Proof that refresh token IS stored in Keychain:

$ security find-generic-password -s "Claude Code-credentials" -w | python3 -c "
import json,sys
d=json.loads(sys.stdin.read())
for k,v in d.get('mcpOAuth',{}).items():
    if 'circleback' in k:
        print(f'hasRefreshToken: {bool(v.get(\"refreshToken\"))}')
        print(f'expiresAt: {v.get(\"expiresAt\")}')
"
# Output:
# hasRefreshToken: True
# expiresAt: 1770978132539

Steps to Reproduce

  1. Install Circleback MCP plugin (HTTP type, https://app.circleback.ai/api/mcp)
  2. Authenticate via /mcp → Circleback → Authenticate
  3. Verify tokens are saved: access token + refresh token both present in Keychain
  4. Wait >24 hours (or until access token expires)
  5. Start a new Claude Code session
  6. Observe: "Token expired without refresh token" in debug logs
  7. Claude Code requires full re-authentication instead of using the stored refresh token

Key Observation

This is not the same as #7744 (where scopes_supported is ignored and no refresh token is issued). In this case:

  • The OAuth server (Circleback) does issue a refresh token
  • Claude Code does save it to Keychain
  • But Claude Code fails to read it back on the next session, reporting "without refresh token"

This is also not the same as #19456 (Keychain write failures after updates). There are no Keychain errors in the logs — the token simply isn't being loaded.

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.39

Platform

Anthropic API (Max)

Operating System

macOS 15.3 (Sequoia)

Terminal/Shell

iTerm2

Additional Information

  • Tested with Circleback MCP plugin (type: "http")
  • Access token lifetime: 86400 seconds (24 hours)
  • Scope: user
  • The issue is reproducible on every session restart after token expiry
  • Related issues: #7744, #19456, #12614 (closed)

View original on GitHub ↗

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