opencode icon indicating copy to clipboard operation
opencode copied to clipboard

feat(auth): add OAuth token keepalive via Messages API

Open mguttmann opened this issue 3 days ago • 1 comments

Summary

Prevents OAuth token expiration during long periods of inactivity by proactively refreshing tokens before they expire and sending periodic ping requests to the Anthropic Messages API.

Fixes #9121 Closes #6559 Closes #4992

Problem

OAuth tokens expire after 1-2 hours of inactivity, causing "Token refresh failed: 400" errors. The previous approach in PR #9112 used /api/oauth/usage endpoint polling, which does not actually keep tokens active - it only retrieves usage statistics without extending the OAuth session lifetime.

The existing token refresh logic in opencode-anthropic-auth plugin only triggers during API calls. If the app is idle, no refresh happens and tokens expire.

Solution

Proactive token keepalive that runs every 30 minutes:

  1. Checks token expiry - If token expires within 10 minutes, refresh it
  2. Refreshes via OAuth endpoint - Uses POST /v1/oauth/token with grant_type: refresh_token
  3. Updates stored tokens - Saves refreshed tokens to auth.json
  4. Pings Messages API - Sends minimal request to maintain session activity
// packages/opencode/src/auth/keepalive.ts

// Token refresh
const response = await fetch("https://console.anthropic.com/v1/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "refresh_token",
    refresh_token: record.refresh,
    client_id: "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
  }),
})

// Session ping
await fetch("https://api.anthropic.com/v1/messages", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${record.access}`,
    "anthropic-version": "2023-06-01",
    "anthropic-beta": "oauth-2025-04-20",
  },
  body: JSON.stringify({
    model: "claude-sonnet-4-20250514",
    max_tokens: 5,
    messages: [{ role: "user", content: "ping" }],
  }),
})

Key Functions

Function Purpose
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
init() Starts 30-minute interval timer

Changes

  • New: packages/opencode/src/auth/keepalive.ts - Keepalive service

    • Runs every 30 minutes (first ping after 1 minute)
    • Refreshes tokens 10 minutes before expiry
    • Uses same client_id as opencode-anthropic-auth plugin
    • Logs under auth.keepalive service
  • Modified: packages/opencode/src/project/bootstrap.ts

    • Initializes AuthKeepAlive on startup

Testing

  1. Start OpenCode with Anthropic OAuth authentication
  2. Leave idle for 2+ hours
  3. Check logs for auth.keepalive service:
    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": "..."}
    
  4. Resume usage - no token expiration errors

Token Cost

  • ~10 tokens per ping (input + output)
  • ~480 tokens/day with 30-minute pings
  • Negligible cost vs. user experience improvement

Note

App must be running for keepalive to work. If the app is closed, tokens will still expire normally.

mguttmann avatar Jan 17 '26 19:01 mguttmann

The following comment was made by an LLM, it may be inaccurate:

No duplicate PRs found

github-actions[bot] avatar Jan 17 '26 19:01 github-actions[bot]

Closing this PR - the token keepalive approach doesn't work as expected. Despite proactive token refresh every 30 minutes, users still experience 'Token refresh failed: 400' errors after overnight inactivity.

The root cause appears to be deeper in Anthropic's OAuth session management - the refresh token itself may become invalid after extended periods, not just the access token.

Will investigate alternative solutions.

mguttmann avatar Jan 19 '26 06:01 mguttmann