- Setup wizard with auto-detection of OpenClaw paths and Claude CLI - Token sync watcher (inotifywait) for real-time credential updates - Auto-refresh trigger timer that runs Claude CLI every 30 min - Supports Claude CLI in Docker container or on host - Temporary ANTHROPIC_BASE_URL override for container environments - Anthropic model configuration for OpenClaw - Auth profile management (fixes key vs access field) - Systemd services and timers for both sync and trigger - Comprehensive documentation and troubleshooting guides - Re-authentication notification system Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
119 lines
6.4 KiB
Markdown
119 lines
6.4 KiB
Markdown
# Architecture
|
|
|
|
## Token Flow Diagram
|
|
|
|
```
|
|
+--------------------+ auto-refresh +------------------------------------------+
|
|
| Claude Code CLI | ================> | .credentials.json |
|
|
| (inside | (every ~8 hours, | { |
|
|
| claude-proxy | built-in to CLI) | "claudeAiOauth": { |
|
|
| container) | | "accessToken": "sk-ant-oat01-...", |
|
|
+--------------------+ | "refreshToken": "sk-ant-ort01-...", |
|
|
| "expiresAt": 1772120060006 |
|
|
| } |
|
|
| } |
|
|
+-------------------+-----------------------+
|
|
|
|
|
inotifywait detects
|
|
CLOSE_WRITE / MOVED_TO
|
|
|
|
|
+-------------------v-----------------------+
|
|
| sync-oauth-token.sh |
|
|
| (systemd service, runs continuously) |
|
|
+---+----------------+----------------+-----+
|
|
| | |
|
|
+----------------+ +-----------+---------+ |
|
|
| | | |
|
|
+---------v---------+ +--------v--------+ +--------v--------+
|
|
| oauth.json | | .env | | docker compose |
|
|
| { | | ANTHROPIC_ | | down/up gateway |
|
|
| "anthropic": { | | OAUTH_TOKEN= | | (reloads env) |
|
|
| "access":..., | | "sk-ant-oat01-" | +---------+--------+
|
|
| "refresh":...,| +-----------------+ |
|
|
| "expires":... | +----------v----------+
|
|
| } | | OpenClaw Gateway |
|
|
| } | | (fresh token loaded |
|
|
+--------+----------+ | from container env) |
|
|
| +----------+----------+
|
|
| mergeOAuthFileIntoStore() |
|
|
| (reads on startup) |
|
|
+-------------------->+ |
|
|
| +--------v---------+
|
|
+------------->| api.anthropic.com|
|
|
| Claude Opus 4.6 |
|
|
+------------------+
|
|
```
|
|
|
|
## Volume Mounts (Docker)
|
|
|
|
```
|
|
HOST PATH CONTAINER PATH
|
|
========= ==============
|
|
|
|
Gateway container (openclaw-openclaw-gateway-1):
|
|
/root/.openclaw/ -> /home/node/.openclaw/
|
|
/root/.openclaw/credentials/oauth.json -> /home/node/.openclaw/credentials/oauth.json
|
|
/root/.openclaw/agents/*/agent/auth-profiles.json -> /home/node/.openclaw/agents/*/agent/auth-profiles.json
|
|
/home/node/.claude/ -> /home/node/.claude/
|
|
/root/openclaw/.env -> loaded as container env vars (at creation time only)
|
|
|
|
Claude CLI container (claude-proxy):
|
|
/root/.openclaw/workspaces/workspace-claude-proxy/
|
|
config/ -> /root/
|
|
config/.claude/.credentials.json -> /root/.claude/.credentials.json
|
|
```
|
|
|
|
## Auth Resolution Order (inside gateway)
|
|
|
|
When the gateway needs to authenticate with Anthropic:
|
|
|
|
```
|
|
1. resolveApiKeyForProvider("anthropic")
|
|
2. -> resolveAuthProfileOrder()
|
|
3. -> reads agents/<agent>/agent/auth-profiles.json
|
|
4. -> isValidProfile() checks each profile:
|
|
5. - type:"api_key" -> requires cred.key
|
|
6. - type:"oauth" -> requires cred.access (NOT cred.key!)
|
|
7. - type:"token" -> requires cred.token
|
|
8. -> If valid profile found: use it
|
|
9. -> If no valid profile: resolveEnvApiKey("anthropic")
|
|
10. -> Reads ANTHROPIC_OAUTH_TOKEN from container env
|
|
11. -> isOAuthToken(key) detects "sk-ant-oat" prefix
|
|
12. -> Uses Bearer auth + Claude Code identity headers
|
|
13. -> Sends request to api.anthropic.com
|
|
|
|
On gateway startup:
|
|
mergeOAuthFileIntoStore()
|
|
-> Reads /home/node/.openclaw/credentials/oauth.json
|
|
-> Merges into auth profile store (if profile doesn't exist)
|
|
```
|
|
|
|
## Why down/up and NOT restart
|
|
|
|
```
|
|
docker compose restart openclaw-gateway
|
|
-> Sends SIGTERM to container process
|
|
-> Restarts the SAME container (same env vars from creation time)
|
|
-> .env changes are NOT reloaded
|
|
-> Result: gateway still has OLD token
|
|
|
|
docker compose down openclaw-gateway && docker compose up -d openclaw-gateway
|
|
-> Stops and REMOVES the container
|
|
-> Creates a NEW container (reads .env fresh)
|
|
-> New env vars are loaded
|
|
-> Result: gateway has NEW token
|
|
```
|
|
|
|
## Source Code References (inside gateway container)
|
|
|
|
| File | Line | Function |
|
|
|------|------|----------|
|
|
| `/app/dist/paths-CyR9Pa1R.js` | 190 | `OAUTH_FILENAME = "oauth.json"` |
|
|
| `/app/dist/paths-CyR9Pa1R.js` | 198-204 | `resolveOAuthDir()` -> `$STATE_DIR/credentials/` |
|
|
| `/app/dist/paths-CyR9Pa1R.js` | 203 | `resolveOAuthPath()` -> joins dir + filename |
|
|
| `/app/dist/model-auth-CmUeBbp-.js` | 3048 | `mergeOAuthFileIntoStore()` -- reads oauth.json |
|
|
| `/app/dist/model-auth-CmUeBbp-.js` | 3358 | `buildOAuthApiKey()` -- returns `credentials.access` |
|
|
| `/app/dist/model-auth-CmUeBbp-.js` | 3832 | `isValidProfile()` -- for oauth, checks `cred.access` |
|
|
| `/app/dist/model-auth-CmUeBbp-.js` | 3942 | `resolveApiKeyForProvider()` -- profiles then env fallback |
|
|
| `/app/dist/model-auth-CmUeBbp-.js` | 4023 | `resolveEnvApiKey("anthropic")` -> reads env var |
|