opencode
opencode copied to clipboard
fix: OAuth tokens expire after inactivity despite /api/oauth/usage polling
Problem
OAuth tokens expire after periods of inactivity (typically 1-2 hours), causing "Token refresh failed: 400" errors when users return to OpenCode. This has been reported in multiple issues:
- #6559: Claude subscription token expires after a period of time
- #4992: Getting "Unauthorized: token expired" during conversation
Root Cause Analysis
The existing token refresh logic in opencode-anthropic-auth plugin only triggers during API calls. If the app is idle or closed:
- No API calls are made
- The access token expires (~1 hour)
- Eventually the refresh token also becomes invalid
- On next use: "Token refresh failed: 400"
The previous fix attempt (PR #9112) using /api/oauth/usage endpoint did not work because:
- That endpoint only returns usage statistics
- It does not refresh the OAuth token
- It does not extend the session lifetime
Solution
Implement a proactive token keepalive that:
-
Refreshes tokens before they expire - Uses Anthropic's OAuth token endpoint (
POST /v1/oauth/token) withgrant_type: refresh_token - Runs every 30 minutes - More frequent to catch expiring tokens
- Refreshes 10 minutes before expiry - Proactive refresh buffer
- Pings with Messages API - After refresh, sends minimal request to maintain session activity
- Updates stored tokens - Saves refreshed tokens to auth.json
Implementation Details
See PR #9122 for the implementation:
New file: packages/opencode/src/auth/keepalive.ts
// Key functions:
- refreshAnthropicToken() - Calls Anthropic OAuth token endpoint
- updateStoredToken() - Persists refreshed token to auth.json
- keepAliveAccount() - Checks expiry, refreshes if needed, then pings
- pingAllAnthropicAccounts() - Processes all OAuth accounts
Token Refresh Flow
Every 30 minutes:
For each Anthropic OAuth account:
1. Check if token expires within 10 minutes
2. If yes: POST /v1/oauth/token (refresh_token grant)
3. Update stored token
4. POST /v1/messages (minimal ping)
Modified: packages/opencode/src/project/bootstrap.ts
- Initializes
AuthKeepAliveon app startup
Testing
- Start OpenCode with Anthropic OAuth authentication
- Leave idle for several hours or overnight
- Check logs for
auth.keepaliveservice:INFO auth.keepalive: starting oauth keepalive {"intervalMs": 1800000} INFO auth.keepalive: token expired or expiring soon, refreshing {"recordId": "...", "expiresIn": 300} INFO auth.keepalive: token refresh successful {"recordId": "..."} INFO auth.keepalive: keepalive ping successful {"recordId": "..."} - Resume usage - no token expiration errors
Important Notes
- Uses the same
client_idasopencode-anthropic-authplugin - App must be running for keepalive to work (cannot prevent expiration if app is closed)
- Token consumption: ~10 tokens per ping (negligible)