feat: auto-refresh expired OAuth token for Claude Code PTY (story 405)

Detect authentication_failed errors from the Claude Code PTY stream
and automatically refresh the OAuth access token using the stored
refresh token in ~/.claude/.credentials.json.

- New module server/src/llm/oauth.rs: reads credentials, calls
  platform.claude.com/v1/oauth/token with JSON body, writes back
- PTY provider detects "error":"authentication_failed" via AtomicBool
- chat_stream retries once after successful refresh
- Clear error message if refresh also fails

On success the retry is transparent. On failure the user sees:
"OAuth session expired. Please run claude login to re-authenticate."

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Timmy
2026-03-26 19:58:04 +00:00
parent ab4ce2db92
commit 710b604b7c
4 changed files with 421 additions and 75 deletions
@@ -0,0 +1,30 @@
---
name: "Auto-refresh expired OAuth token for Claude Code PTY"
---
# Story 405: Auto-refresh expired OAuth token for Claude Code PTY
## User Story
As a storkit user with a Claude Max subscription, I want the server to automatically refresh my expired OAuth token so that chat, Matrix, and WhatsApp integrations don't stop working when the token expires.
## Acceptance Criteria
### Detection
- [ ] When the Claude Code PTY returns an `authentication_failed` error, storkit detects it instead of passing the raw 401 JSON to the user
### Auto-refresh (credentials exist, refresh token valid)
- [ ] Storkit reads the OAuth refresh token from `~/.claude/.credentials.json`
- [ ] Storkit calls the Anthropic OAuth token refresh endpoint (`https://console.anthropic.com/v1/oauth/token` with `grant_type=refresh_token`) to obtain a new access token
- [ ] Storkit writes the refreshed access token (and new expiresAt) back to `~/.claude/.credentials.json`
- [ ] After a successful refresh, storkit automatically retries the original chat request
- [ ] The refresh+retry is transparent to the user — they see no error
### Full login required (no credentials, or refresh token also expired)
- [ ] If `.credentials.json` doesn't exist or the refresh call itself fails, storkit surfaces a clear error: "OAuth session expired. Please run `claude login` to re-authenticate."
- [ ] The error message is surfaced through the normal chat stream (not just server logs)
## Out of Scope
- Implementing the full interactive `claude login` browser OAuth flow inside storkit
- Proactive token refresh before expiry (refreshing on demand when the error occurs is sufficient)