openclaw_oauth_sync/scripts/refresh-claude-token.sh
shamid202 22731fff60 Add complete OAuth token refresh and sync solution
- 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>
2026-02-27 01:51:18 +07:00

126 lines
4.2 KiB
Bash
Executable File

#!/bin/bash
# refresh-claude-token.sh — Directly refreshes Claude Max OAuth token via Anthropic API
# Fallback method for servers without a Claude CLI container doing auto-refresh
# Run via systemd timer every 6 hours
#
# Endpoint: POST https://console.anthropic.com/v1/oauth/token
# Client ID: 9d1c250a-e61b-44d9-88ed-5944d1962f5e (Claude Code public OAuth client)
set -euo pipefail
# --- Configuration (can be overridden via env vars or setup.sh substitution) ---
CLAUDE_CREDS_FILE="${CLAUDE_CREDS_FILE:-@@CLAUDE_CREDS_FILE@@}"
OPENCLAW_OAUTH_FILE="${OPENCLAW_OAUTH_FILE:-@@OPENCLAW_OAUTH_FILE@@}"
OPENCLAW_ENV_FILE="${OPENCLAW_ENV_FILE:-@@OPENCLAW_ENV_FILE@@}"
COMPOSE_DIR="${COMPOSE_DIR:-@@COMPOSE_DIR@@}"
TOKEN_URL="https://console.anthropic.com/v1/oauth/token"
CLIENT_ID="9d1c250a-e61b-44d9-88ed-5944d1962f5e"
LOG_PREFIX="[refresh-claude-token]"
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $LOG_PREFIX $*"; }
error() { echo "$(date '+%Y-%m-%d %H:%M:%S') $LOG_PREFIX ERROR: $*" >&2; }
# Check credentials file exists (source of current refresh token)
if [ ! -f "$CLAUDE_CREDS_FILE" ]; then
error "Credentials file not found: $CLAUDE_CREDS_FILE"
exit 1
fi
# Extract current refresh token
REFRESH_TOKEN=$(python3 -c "
import json, sys
try:
with open('$CLAUDE_CREDS_FILE') as f:
data = json.load(f)
print(data['claudeAiOauth']['refreshToken'])
except Exception as e:
print(f'ERROR: {e}', file=sys.stderr)
sys.exit(1)
")
if [ -z "$REFRESH_TOKEN" ]; then
error "No refresh token found in $CLAUDE_CREDS_FILE"
exit 1
fi
log "Refreshing token..."
# Call Anthropic's OAuth token endpoint
RESPONSE=$(curl -s -X POST "$TOKEN_URL" \
-H "Content-Type: application/json" \
-d "{\"grant_type\":\"refresh_token\",\"refresh_token\":\"$REFRESH_TOKEN\",\"client_id\":\"$CLIENT_ID\"}")
# Parse response and write to all credential locations
python3 -c "
import json, sys, time, os, re
resp = json.loads('''$RESPONSE''')
if 'access_token' not in resp:
print('$LOG_PREFIX ERROR: Refresh failed:', json.dumps(resp), file=sys.stderr)
sys.exit(1)
access_token = resp['access_token']
refresh_token = resp['refresh_token']
expires_at = int(time.time() * 1000) + (resp['expires_in'] * 1000)
scopes = resp.get('scope', 'user:inference user:mcp_servers user:profile user:sessions:claude_code').split(' ')
# 1. Write Claude CLI credentials format (keeps refresh token for next run)
claude_creds = {
'claudeAiOauth': {
'accessToken': access_token,
'refreshToken': refresh_token,
'expiresAt': expires_at,
'scopes': scopes,
'subscriptionType': 'max',
'rateLimitTier': 'default_claude_max_5x'
}
}
with open('$CLAUDE_CREDS_FILE', 'w') as f:
json.dump(claude_creds, f)
print(f'Updated $CLAUDE_CREDS_FILE')
# 2. Update ANTHROPIC_OAUTH_TOKEN in .env
with open('$OPENCLAW_ENV_FILE') as f:
env = f.read()
if 'ANTHROPIC_OAUTH_TOKEN' in env:
env = re.sub(r'ANTHROPIC_OAUTH_TOKEN=.*', f'ANTHROPIC_OAUTH_TOKEN=\"{access_token}\"', env)
else:
env = env.rstrip('\n') + f'\nANTHROPIC_OAUTH_TOKEN=\"{access_token}\"\n'
with open('$OPENCLAW_ENV_FILE', 'w') as f:
f.write(env)
print(f'Updated $OPENCLAW_ENV_FILE')
# 3. Write OpenClaw oauth.json (field-mapped)
openclaw_oauth = {
'anthropic': {
'access': access_token,
'refresh': refresh_token,
'expires': expires_at,
'scopes': scopes,
'subscriptionType': 'max',
'rateLimitTier': 'default_claude_max_5x'
}
}
os.makedirs(os.path.dirname('$OPENCLAW_OAUTH_FILE'), exist_ok=True)
with open('$OPENCLAW_OAUTH_FILE', 'w') as f:
json.dump(openclaw_oauth, f)
print(f'Updated $OPENCLAW_OAUTH_FILE')
expires_hours = resp['expires_in'] // 3600
account = resp.get('account', {}).get('email_address', 'unknown')
print(f'OK: account={account}, expires in {expires_hours}h, token={access_token[:20]}...')
"
if [ $? -ne 0 ]; then
error "Token refresh failed"
exit 1
fi
# 4. Recreate gateway container (down/up, NOT restart)
cd "$COMPOSE_DIR"
log "Recreating gateway container..."
docker compose down openclaw-gateway 2>&1 | sed "s/^/$LOG_PREFIX /"
docker compose up -d openclaw-gateway 2>&1 | sed "s/^/$LOG_PREFIX /"
log "Gateway recreated with new token"