- 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>
6.4 KiB
6.4 KiB
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 |