Automatic Anthropic OAuth token refresh and sync for OpenClaw. Keeps your Claude Max tokens alive indefinitely.
Go to file
2026-03-01 13:54:22 +01:00
configs policies: add standard policy files, formatting, and secret scanning 2026-03-01 07:28:54 +00:00
docs feat: merge Gitea webhook security into setup wizard (issue #2) 2026-03-01 08:43:02 +00:00
scripts feat: merge Gitea webhook security into setup wizard (issue #2) 2026-03-01 08:43:02 +00:00
templates feat: merge Gitea webhook security into setup wizard (issue #2) 2026-03-01 08:43:02 +00:00
tests feat: merge Gitea webhook security into setup wizard (issue #2) 2026-03-01 08:43:02 +00:00
tools policies: add standard policy files, formatting, and secret scanning 2026-03-01 07:28:54 +00:00
.editorconfig policies: add standard policy files, formatting, and secret scanning 2026-03-01 07:28:54 +00:00
.gitignore policies: add standard policy files, formatting, and secret scanning 2026-03-01 07:28:54 +00:00
.prettierignore policies: add standard policy files, formatting, and secret scanning 2026-03-01 07:28:54 +00:00
.prettierrc policies: add standard policy files, formatting, and secret scanning 2026-03-01 07:28:54 +00:00
.secret-scan-allowlist feat: merge Gitea webhook security into setup wizard (issue #2) 2026-03-01 08:43:02 +00:00
LICENSE Add complete OAuth token refresh and sync solution 2026-02-27 01:51:18 +07:00
Makefile feat: merge Gitea webhook security into setup wizard (issue #2) 2026-03-01 08:43:02 +00:00
package-lock.json policies: add standard policy files, formatting, and secret scanning 2026-03-01 07:28:54 +00:00
package.json policies: add standard policy files, formatting, and secret scanning 2026-03-01 07:28:54 +00:00
README.md feat: merge Gitea webhook security into setup wizard (issue #2) 2026-03-01 08:43:02 +00:00
setup.sh feat: merge Gitea webhook security into setup wizard (issue #2) 2026-03-01 08:43:02 +00:00

OAuth Fix for OpenClaw + Claude Max

Created by ROOH

Automatic Anthropic OAuth token refresh for OpenClaw. Keeps your Claude Max tokens alive indefinitely.

The Problem

OpenClaw uses Anthropic Claude models (e.g., claude-opus-4-6) via OAuth tokens from Claude Max subscriptions. These tokens expire every ~8 hours. Without automated refresh, all agents stop working with:

HTTP 401 authentication_error: OAuth token has expired

The Solution

Two services working together:

  1. Auto-refresh trigger — A timer that runs Claude CLI every 30 minutes to check and refresh the token before it expires. Supports Claude CLI in a Docker container or installed on the host.
  2. Token sync watcher — An inotifywait service that detects when Claude CLI writes new credentials and instantly syncs them to OpenClaw.
Timer (every 30min)      Claude Code CLI          sync-oauth-token.sh         OpenClaw Gateway
(triggers CLI when  ---> (refreshes token,   ---> (watches for changes,  ---> (gets fresh token)
 token near expiry)       writes creds file)       syncs via inotifywait)

Quick Start

git clone <repository-url>
cd openclaw_oauth_sync
sudo ./setup.sh

The interactive wizard will:

  1. Check prerequisites (offers to install python3, curl, inotify-tools if missing)
  2. Detect your OpenClaw installation paths
  3. Find Claude CLI credentials (offers to install CLI and help with sign-in if needed)
  4. Configure the Anthropic model (if not already set up)
  5. Install the token sync watcher (inotifywait or timer fallback)
  6. Detect Claude CLI (container or host) and install the auto-refresh trigger
  7. Test the CLI invocation to confirm it works
  8. Verify everything works

Every install step asks for your confirmation first — you have full control over what gets installed.

Webhook Security (Optional)

The setup wizard includes an optional Step 11 that installs 5-layer webhook security for the /hooks/gitea endpoint. This protects your OpenClaw instance from unauthorized webhook requests.

Security layers:

  1. IP allowlisting (only your Gitea server can send webhooks)
  2. Rate limiting (10 req/s, burst 20)
  3. Payload size limit (1MB)
  4. HMAC-SHA256 signature verification (njs module)
  5. Per-repository allowlist (only approved repos can trigger agents)

What gets installed (when you choose to enable it during setup):

  • nginx njs HMAC module (/etc/nginx/njs/gitea-hmac-verify.js)
  • Webhook secret file (/etc/nginx/gitea-webhook-secret)
  • Repository allowlist (/etc/nginx/gitea-repo-allowlist.json)
  • Helper scripts (/opt/webhook-security/scripts/)
  • gitea-approve-repo command (add repos to allowlist)

After installation, manage the allowlist with:

gitea-approve-repo owner/repo        # Allow a specific repo
cat /etc/nginx/gitea-repo-allowlist.json  # View current allowlist

Full documentation: docs/WEBHOOK-SECURITY.md Security audit: docs/SECURITY-AUDIT.md

Prerequisites

  • Linux server with systemd
  • Docker + Docker Compose v2
  • OpenClaw installed and running
  • Claude Max subscription with OAuth credentials
  • Claude Code CLI — either in a Docker container or installed on the host (wizard can install it)
  • python3 (wizard offers to install if missing)
  • curl (wizard offers to install if missing)
  • inotify-tools (optional, wizard offers to install if missing)

How It Works

Architecture

+---------------------------+
| trigger-claude-refresh.sh |  (systemd timer, every 30 min)
| checks token expiry       |
| if < 1.5h remaining:      |
|   triggers Claude CLI     |
+-------------+-------------+
              |
              v
+--------------------+     auto-refresh     +-------------------+
| Claude Code CLI    |  =================>  | .credentials.json |
| (container or host)|  (token near expiry) +--------+----------+
+--------------------+                               |
                                          inotifywait detects change
                                                     |
                                          +----------v----------+
                                          | sync-oauth-token.sh |
                                          +--+------+------+----+
                                             |      |      |
                                    oauth.json   .env   gateway
                                    (mapped     (env    restart
                                     fields)    var)    (down/up)

Token Flow

  1. trigger-claude-refresh.sh runs every 30 minutes, checks token expiry
  2. If token has < 1.5 hours remaining, triggers Claude CLI
  3. Claude CLI detects its token is near expiry
  4. CLI calls Anthropic's refresh endpoint, gets new access token
  5. CLI writes updated .credentials.json
  6. inotifywait detects the file change (< 1 second)
  7. sync-oauth-token.sh reads the new token
  8. Maps fields: accessToken -> access, refreshToken -> refresh, expiresAt -> expires
  9. Writes to oauth.json (OpenClaw's format)
  10. Updates ANTHROPIC_OAUTH_TOKEN in .env
  11. Recreates gateway container (docker compose down/up — NOT restart!)
  12. Gateway starts with the fresh token

ANTHROPIC_BASE_URL Override

If Claude CLI runs in a container with ANTHROPIC_BASE_URL set to a proxy (e.g., LiteLLM), the trigger script uses a temporary per-invocation override:

docker exec -e ANTHROPIC_BASE_URL=https://api.anthropic.com container claude -p "say ok"

The -e flag overrides the env var only for that single command. The container's running processes are unaffected. This is detected and configured automatically by the wizard.

Re-authentication Notification

If the refresh token itself expires (e.g., subscription lapsed), the trigger script:

  • Creates a flag file at REAUTH_NEEDED in the OpenClaw directory
  • Logs an error to journalctl
  • Future: Mattermost webhook notification

Why down/up and NOT restart?

docker compose restart does NOT reload .env variables. It restarts the same container with the same environment. Only docker compose down + docker compose up -d creates a new container that reads .env fresh.

Anthropic Model Configuration

OpenClaw has a built-in Anthropic provider. Do NOT add anthropic to models.providers in openclaw.json — it causes double /v1 in URLs resulting in 404 errors.

The correct configuration (set by the wizard):

{
  "agents": {
    "defaults": {
      "model": {
        "primary": "anthropic/claude-opus-4-6"
      },
      "models": {
        "anthropic/claude-opus-4-6": { "alias": "Claude Opus 4.6 (Max)" },
        "anthropic/claude-sonnet-4-6": { "alias": "Claude Sonnet 4.6 (Max)" }
      }
    }
  }
}

See docs/OPENCLAW-MODEL-CONFIG.md for full details.

Credential Field Mapping

Claude CLI and OpenClaw use different field names:

Claude CLI (.credentials.json) OpenClaw (oauth.json)
claudeAiOauth.accessToken anthropic.access
claudeAiOauth.refreshToken anthropic.refresh
claudeAiOauth.expiresAt anthropic.expires

See docs/FIELD-MAPPING.md for all formats.

Manual Installation

If you prefer not to use the wizard:

1. Install inotify-tools

apt install inotify-tools

2. Edit and install the sync script

# Edit scripts/sync-oauth-token.sh — replace @@PLACEHOLDER@@ values:
#   @@CLAUDE_CREDS_FILE@@  = path to Claude CLI .credentials.json
#   @@OPENCLAW_OAUTH_FILE@@ = path to OpenClaw oauth.json
#   @@OPENCLAW_ENV_FILE@@  = path to OpenClaw .env
#   @@COMPOSE_DIR@@        = path to OpenClaw docker-compose directory

cp scripts/sync-oauth-token.sh /usr/local/bin/
chmod +x /usr/local/bin/sync-oauth-token.sh

3. Install systemd service

# Edit templates/sync-oauth-token.service — replace @@SYNC_SCRIPT_PATH@@
cp templates/sync-oauth-token.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now sync-oauth-token.service

4. Install the auto-refresh trigger

# Edit scripts/trigger-claude-refresh.sh — replace @@PLACEHOLDER@@ values:
#   @@CREDS_FILE@@             = path to Claude CLI .credentials.json
#   @@REAUTH_FLAG@@            = path to REAUTH_NEEDED flag file
#   @@CLI_MODE@@               = "container" or "host"
#   @@CLI_CONTAINER@@          = container name (if container mode)
#   @@CLI_BASE_URL_OVERRIDE@@  = "true" or "false"

cp scripts/trigger-claude-refresh.sh /usr/local/bin/
chmod +x /usr/local/bin/trigger-claude-refresh.sh

# Edit templates/trigger-claude-refresh.service — replace @@TRIGGER_SCRIPT_PATH@@
cp templates/trigger-claude-refresh.service /etc/systemd/system/
cp templates/trigger-claude-refresh.timer /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now trigger-claude-refresh.timer

5. Configure OpenClaw

See docs/OPENCLAW-MODEL-CONFIG.md.

Verification

# Run the health check
./scripts/verify.sh

# Watch sync logs in real-time
journalctl -u sync-oauth-token.service -f

# Check trigger logs
journalctl -u trigger-claude-refresh -n 20

# Check all timers
systemctl list-timers sync-oauth-token* trigger-claude-refresh*

# Check service status
systemctl status sync-oauth-token.service
systemctl status trigger-claude-refresh.timer

Uninstall

./setup.sh --uninstall
# or
./scripts/uninstall.sh

Fallback Method (Timer)

If inotifywait is unavailable, the wizard installs a systemd timer that refreshes the token directly via Anthropic's API every 6 hours. This is less responsive but doesn't require inotify.

Troubleshooting

See docs/TROUBLESHOOTING.md for common issues:

  • Token expired errors
  • docker compose restart not reloading env
  • Auth profile key vs access field
  • 404 from custom anthropic provider
  • Cooldown errors
  • Claude CLI not responding (ANTHROPIC_BASE_URL override)
  • REAUTH_NEEDED flag (refresh token expired)

Documentation

Author

ROOH

License

MIT