feat: auth error classification, streaming buffer inspection, and macOS credential sync#3
Merged
Merged
Conversation
… startup state via logger
…cking outcomes via logger
- Add subscriptionCooldownUntil timestamp tracking - Implement canTryClaudeSubscription() to check cooldown state - Add resetClaudeSubscriptionCircuit() to reset on success - Add enterClaudeSubscriptionCooldown() with configurable cooldown period - Add recordClaudeSubscriptionFailure() to classify and handle errors - Add getClaudeSubscriptionState() to expose current state - Classify provider errors as rate_limit, context_window, or other - Prevents repeated subscription attempts during rate-limit/cooldown - Tests: quota failure handling, cooldown circuit, state transitions
Adds auth_error classification (401/403 without quota body) to classifyProviderError so the circuit breaker enters immediate cooldown on bad/expired credentials instead of burning retry budget. Adds inspectInitialStreamingBuffer to detect error events in the first SSE chunk before flushing to the client, enabling clean failover when a streaming provider returns an error mid-connection.
Container names are now static (cc-proxy, cc-db, cc-adminer) rather than env-var-interpolated to avoid auto-generated names from Compose. Credentials mount corrected from .credentials.json (a directory) to claude-credentials.json and changed to read-write so the sync sidecar can update it in place.
sync-credentials.sh reads the Claude OAuth token from Keychain and writes it to ~/.claude/claude-credentials.json for the Docker mount. setup-credential-sync.sh installs a LaunchAgent that runs the sync every 5 minutes so the proxy always has a fresh token without manual intervention.
Container names are now hardcoded in docker-compose.yml so the env vars PROXY_CONTAINER_NAME, DB_CONTAINER_NAME, ADMINER_CONTAINER_NAME are removed from the example file. Setup scripts and docs updated to reflect ANTHROPIC_API_URL routing and the new settings:update commands.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Auth error circuit breaker —
classifyProviderErrornow returnsauth_errorfor 401 and bare 403 responses (body-based checks run first, so a 403 with quota/billing content still maps torate_limit). The circuit breaker enters immediate cooldown on auth errors instead of burning retry budget on a credential that won't work until fixed.Streaming error buffer inspection —
inspectInitialStreamingBufferparses SSE events in the first chunk before flushing to the client. When a streaming provider returns anevent: erroror a JSON error payload at stream start, the proxy intercepts it cleanly and fails over rather than sending a partial error stream to the CLI.Container names hardcoded —
docker-compose.ymlcontainer names changed from${VAR:-default}to static strings (cc-proxy,cc-db,cc-adminer) to prevent Compose from generating auto-suffixed names. The now-redundant env vars removed from.env.docker.example.Credentials mount fixed — Volume mount corrected from
~/.claude/.credentials.json(which was a directory, silently breaking OAuth reads) to~/.claude/claude-credentials.json, mounted read-write.macOS Keychain credential sync —
scripts/sync-credentials.shreads the Claude OAuth token from the macOS Keychain (Claude Code-credentialsservice) and writes it to~/.claude/claude-credentials.json.scripts/setup-credential-sync.shinstalls a LaunchAgent that runs this sync every 5 minutes so the proxy always has a fresh token without manual intervention.Test plan
npx tsc --noEmit— passes cleannpm run lint— no warningsnpm test— 71/71 pass, including the quota-style 403 regression test (classifies quota-style provider responses as rate limits)launchctl list | grep com.claude)