-
Notifications
You must be signed in to change notification settings - Fork 8.6k
Closed
Description
Problem
OAuth tokens expire after ~12 hours of inactivity, causing "Token refresh failed: 400" errors when users return to OpenCode. This is a widespread issue affecting many users:
- Claude subscription token expires after a period of time #6559: "Usually it happens when I stopped using for a while, e.g. 12 hours"
- Getting "Unauthorized: token expired" during conversation #4992: "Getting 'Unauthorized: token expired' during conversation"
- The Claude Subscription Issue is back #8544: "The Claude Subscription Issue is back"
Current workaround: Users must manually re-login every morning, which disrupts workflow.
Root cause: Anthropic's OAuth refresh tokens become invalid after extended inactivity periods. This cannot be prevented by periodic token refresh or API pings - the session itself expires server-side.
Proposed Solution
Implement persistent headless browser sessions that can automatically refresh OAuth tokens when 400 errors occur.
How it works
- One-time setup: User runs
opencode auth browser setup, browser opens, user logs into claude.ai - Session persists: Browser profile saved to
~/.opencode/browsers/anthropic/<account-id>/ - Auto-refresh: When a 400 error occurs:
- Headless browser launches with saved profile (already logged in)
- Navigates to OAuth authorize URL
- Automatically redirected to callback (no user interaction needed)
- New tokens extracted and saved
- Original request retried
Architecture
┌─────────────────┐ ┌──────────────┐ ┌─────────────────────┐
│ OpenCode │────▶│ Auth Module │────▶│ Headless Browser │
│ (API Call) │ │ (Error 400) │ │ (Playwright) │
└─────────────────┘ └──────────────┘ └─────────────────────┘
│ │
▼ ▼
┌──────────────┐ ┌─────────────────────┐
│ Retry with │◀────│ New OAuth Token │
│ new Token │ │ (auto-extracted) │
└──────────────┘ └─────────────────────┘
Features
Multi-Account Support
Each OAuth account gets its own browser profile, enabling auto-relogin for all accounts:
~/.opencode/browsers/anthropic/
├── <account-1-id>/ # Browser profile for Account 1
├── <account-2-id>/ # Browser profile for Account 2
└── <account-n-id>/ # Browser profile for Account N
CLI Commands
# Setup browser session for an account
$ opencode auth browser setup
◆ Select account to configure:
│ ○ account-1@email.com
│ ● account-2@email.com
│
◐ Opening browser... Please log in to claude.ai
◐ Login detected. Saving session...
└ Browser session configured for account-2@email.com
# Check status of all browser sessions
$ opencode auth browser status
Account 1 (account-1@email.com): ● Active (last refresh: 2h ago)
Account 2 (account-2@email.com): ○ Not configured
Account 3 (account-3@email.com): ● Active (last refresh: 5min ago)
# Remove a browser session
$ opencode auth browser remove
◆ Select account to remove session:
│ ● account-1@email.com
│
└ Browser session removedDesktop UI Integration
In Settings → Providers → Anthropic → [Account]:
┌─────────────────────────────────────────────────┐
│ Account: account@email.com │
├─────────────────────────────────────────────────┤
│ Usage: ████████░░ 80% │
│ │
│ ─────────────────────────────────────────────── │
│ │
│ 🔄 Auto-Relogin │
│ │
│ Status: ● Enabled │
│ Last Refresh: 2 hours ago │
│ │
│ [Configure Session] [Remove Session] │
│ │
├─────────────────────────────────────────────────┤
│ [Switch to this Account] [Delete Account] │
└─────────────────────────────────────────────────┘
Technical Implementation
New Files
| File | Purpose |
|---|---|
packages/opencode/src/auth/browser.ts |
Headless browser session management |
packages/opencode/src/auth/auto-relogin.ts |
Error detection & auto-refresh logic |
Modified Files
| File | Changes |
|---|---|
packages/opencode/src/auth/index.ts |
Add browserSession field to OAuthRecord |
packages/opencode/src/cli/cmd/auth.ts |
Add browser subcommands |
packages/app/src/components/dialog-settings.tsx |
Add Auto-Relogin UI section |
packages/opencode/package.json |
Add Playwright dependency |
Auth Store Schema Extension
const OAuthRecord = z.object({
// ... existing fields ...
browserSession: z.object({
enabled: z.boolean(),
profilePath: z.string(),
lastRefresh: z.number().optional(),
lastError: z.string().optional(),
}).optional(),
})API Endpoints
| Endpoint | Purpose |
|---|---|
POST /auth/browser/setup |
Initiate browser session setup |
GET /auth/browser/status |
Get browser session status for all accounts |
DELETE /auth/browser/:recordId |
Remove browser session for account |
Dependencies
- Requires feat: Multi-Account OAuth Rotation with Settings UI #9068 (Multi-Account OAuth) for:
- Per-account OAuth record management
- Settings UI infrastructure
- Account selection in CLI
Security Considerations
- Browser profiles stored locally in user's data directory
- No credentials transmitted over network (browser handles auth natively)
- Sessions can be removed at any time via CLI or UI
- Headless browser runs only when needed (not persistent daemon)
Acceptance Criteria
-
opencode auth browser setupopens browser for login - Browser session persists after setup
- 400 errors trigger automatic token refresh
- Multi-account: each account has separate session
- CLI shows browser session status
- Desktop UI shows status and config options
- Sessions can be removed via CLI and UI
- Works on macOS, Linux, Windows
Related Issues
- Claude subscription token expires after a period of time #6559 - Claude subscription token expires after a period of time
- Getting "Unauthorized: token expired" during conversation #4992 - Getting "Unauthorized: token expired" during conversation
- The Claude Subscription Issue is back #8544 - The Claude Subscription Issue is back
- OAuth token expires after inactivity causing 'Token refresh failed: 400' #9111 - OAuth token expires after inactivity (closed, solution didn't work)
Metadata
Metadata
Assignees
Labels
No labels