OAuth token refresh endpoint and reliability fixes
NousResearch/hermes-agent · cluster 2962 · agent
Data as of
Nothing open on GitHub. Fix merged in #3246. Check it covers your case.
The short answer
0open issues
0open PRs
4items grouped
- Known?
- Yes. 4 related items grouped here, 2 issues already closed.
- Fix event
- A fix was merged to main in PR #3246. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
unresolved. That is separate from the fix event. - Still open
- Nothing open on GitHub.
Multiple issues and fixes addressing OAuth token refresh failures, including wrong endpoints causing 401/403 errors, migration to platform.claude.com, atomic credential writes, stale detection, and removal of an unused PKCE flow.
Open issues0
Open PRs0
Fixes on record
-
#3246 fix(auth): migrate OAuth token refresh to platform.claude.com with fallback
files:
agent/anthropic_adapter.pyShow the diff
-
agent/anthropic_adapter.py@@ -210,9 +210,12 @@ def _refresh_oauth_token(creds: Dict[str, Any]) -> Optional[str]: Only works for credentials that have a refresh token (from claude /login or claude setup-token with OAuth flow). + Tries the new platform.claude.com endpoint first (Claude Code >=2.1.81), + then falls back to console.anthropic.com for older tokens. + Returns the new access token, or None if refresh fails. """ - import urllib.parse + import time import urllib.request refresh_token = [redacted:secret-assignment]"refreshToken", "") @@ -223,38 +226,42 @@ def _refresh_oauth_token(creds: Dict[str, Any]) -> Optional[str]: # Client ID used by Claude Code's OAuth flow CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e" - data = urllib.parse.urlencode({ + # Anthropic migrated OAuth from console.anthropic.com to platform.claude.com + # (Claude Code v2.1.81+). Try new endpoint first, fall back to old. + token_endpoints = [ + "https://platform.claude.com/v1/oauth/token", + "https://console.anthropic.com/v1/oauth/token", + ] + + payload = json.dumps({ "grant_type": "refresh_token", "refresh_token": refresh_token, "client_id": CLIENT_ID, }).encode() - req = urllib.request.Request( - "https://console.anthropic.com/v1/oauth/token", - data=data, - headers={ - "Content-Type": "application/x-www-form-urlencoded", - "User-Agent": f"claude-cli/{_CLAUDE_CODE_VERSION} (external, cli)", - }, - method="POST", - ) + headers = { + "Content-Type": "application/json", + "User-Agent": f"claude-cli/{_CLAUDE_CODE_VERSION} (external, cli)", + } - try: - with urllib.request.urlopen(req, timeout=10) as resp: - result = json.loads(resp.read().decode()) - new_access = result.get("access_token", "") - new_refresh = result.get("refresh_token", refresh_token) - expires_in = result.get("expires_in", 3600) # seconds - - if new_access: - import time - new_expires_ms = int(time.time() * 1000) + (expires_in * 1000) - # Write refreshed credentials back to ~/.claude/.credentials.json - _write_claude_code_credentials(new_access, new_refresh, new_expires_ms) - logger.debug("Successfully refreshed Claude Code OAuth token") - return new_access - except Exception as e: - logger.debug("Failed to refresh Claude Code token: %s", e) + for endpoint in token_endpoints: + req = urllib.request.Request( + endpoint, data=payload, headers=headers, method="POST", + ) + try: + with urllib.request.urlopen(req, timeout=10) as resp: + result = json.loads(resp.read().decode()) + new_access = result.get("access_token", "") + new_refresh = result.get("refresh_token", refresh_token) + expires_in = result.get("expires_in", 3600) + + if new_access: + new_expires_ms = int(time.time() * 1000) + (expires_in * 1000) + _write_claude_code_credentials(new_access, new_refresh, new_expires_ms) + logger.debug("Refreshed Claude Code OAuth token via %s", endpoint) + return new_access + except Exception as e: + logger.debug("Token refresh failed at %s: %s", endpoint, e) return None
-
1 PR closed without merging
From the close comments: superseded by another PR 1
-
#2968 fix(oauth): use platform.claude.com endpoint for token refresh + startup warning · superseded by another PR →
#3246
Closed issues · 2
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.39: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- agent
- Merged PRs
- #3246
- Closed issues
- 2
- Attempts
- 1 PR closed without merging
- Fix link checked
- 26 Mar 2026. This confirmation is old and may be out of date (a revert or reopen would not have invalidated it here); re-verify.