[PR #9122] feat(auth): add OAuth token keepalive via Messages API #12989

Closed
opened 2026-02-16 18:17:52 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/9122

State: closed
Merged: No


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 (using standard OAuth form-urlencoded format)
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",
  }).toString(),
})

// Session ping (minimal tokens, uses cheapest model)
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-3-haiku-20240307",
    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 30 seconds)
    • 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.

**Original Pull Request:** https://github.com/anomalyco/opencode/pull/9122 **State:** closed **Merged:** No --- ## 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 ```typescript // packages/opencode/src/auth/keepalive.ts // Token refresh (using standard OAuth form-urlencoded format) 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", }).toString(), }) // Session ping (minimal tokens, uses cheapest model) 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-3-haiku-20240307", 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 30 seconds) - 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.
yindo added the pull-request label 2026-02-16 18:17:52 -05:00
yindo closed this issue 2026-02-16 18:17:52 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#12989