#Docker: Token mismatch: autogenerated token: macOS 26.2

1 messages · Page 1 of 1 (latest)

open palm
#

OS: macOS Tahoe 26.2
Chip/Memory: M2 Max/64GB

I am trying to run openclaw on docker. I cloned the repo and ran ./docker-setup.sh. I did the full setup, with local gateway/LAN (0.0.0.0:18789) using OpenAI free API key for testing. I skipped agents/skills setup, and the script finished. Then, I used docker compose to start the gateway. Here I run into the following problems:

the openclaw-cli container does not connect to the gateway with a token mismatch error. The token was auto generated by the setup script.
The control UI shows a keys-mismatch error if I maunally paste the key in ~/.openclaw/openclaw.json
The key in ~/.openclaw/openclaw.json matches the key in /home/node/.openclaw.json in the gateway container

I deleted the container and images (using docker system prune -a --volumes) , removed !/.openclaw on the mac and reran docker-setup.sh several times on the mac - but keep running into this problem every time. Before trying to run on docker, I did set up openclaw using the native install script, and then uninstalled it because of security issues.

Where can the container be picking up another token? I have tried grepping (grep -rin OPENCLAW_GATEWAY_TOKEN) in the ~/.openclaw and the openclaw git directories without seeing anything that appeared odd to me. Any ideas how to troubleshoot this?

tame perchBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> âś… Mark Solution

quick hollowBOT
# open palm OS: macOS Tahoe 26.2 Chip/Memory: M2 Max/64GB I am trying to run openclaw on do...

A “gateway token mismatch” almost always means the Gateway is reading the token from a different place than the client/UI is.

There are two separate concepts to keep straight:

  • Gateway expects: gateway.auth.token (or env OPENCLAW_GATEWAY_TOKEN)
  • CLI uses to call a gateway: gateway.remote.token (and it must match gateway.auth.token on the gateway)

Docs refs:

1) First suspect: OPENCLAW_GATEWAY_TOKEN is being overridden by your shell env (host)

Docker Compose loads .env, but your terminal environment wins if you have OPENCLAW_GATEWAY_TOKEN exported somewhere (common if you previously tested a native install).

On your Mac (same shell you run docker compose from):

#
env | grep OPENCLAW_GATEWAY_TOKEN

If that prints anything, temporarily remove it and retry:

unset OPENCLAW_GATEWAY_TOKEN
docker compose up -d

Also check your shell startup files (~/.zshrc, ~/.zprofile, etc.) for a lingering export.

2) Second suspect: you’re running docker compose from a directory that isn’t using the .env you think

Compose only auto-loads .env from the current working directory (where you run the command).

From the repo root (where docker-compose.yml is), verify:

#
pwd
ls -la .env
grep OPENCLAW_GATEWAY_TOKEN .env

If you run compose elsewhere, force the env file explicitly:

docker compose --env-file /path/to/openclaw-repo/.env up -d

3) Prove what token each container actually sees (without pasting secrets)

Compare hashes (safe to share hashes if needed):

#
docker compose exec openclaw-gateway sh -lc 'echo -n "$OPENCLAW_GATEWAY_TOKEN" | shasum -a 256 | cut -d" " -f1'
docker compose run --rm openclaw-cli sh -lc 'echo -n "$OPENCLAW_GATEWAY_TOKEN" | shasum -a 256 | cut -d" " -f1'

Those two outputs should be identical. If they’re not, you’ve found the mismatch source (compose env resolution / multiple env files / host env override).

4) About the Control UI “keys mismatch” error

If you open the UI via a LAN IP (e.g. http://192.168.x.y:18789/) the browser is in a non-secure context (WebCrypto can be blocked), and OpenClaw can reject the connection with device-identity related errors that look like “keys mismatch”.

Fix: open it locally as secure-localhost:

  • http://127.0.0.1:18789/

And if you’ve been iterating a lot, also try a clean browser state (incognito or clear site data for that origin) so you’re not fighting stale localStorage token/device data.

5) If you paste the exact CLI error line, I can narrow it down

#

Specifically, the full line that contains unauthorized: ... mismatch (...) (it usually hints whether it thinks you need to set gateway.remote.token vs UI token).

If you want, paste:

  • the exact CLI error text (no tokens)
  • and the two SHA-256 hashes from step (3) above
open palm
#

First suspect: shell env overriding OPEN_CLAW_GATEWAY_TOKEN: is not the case.

  • env | grep OPENCLAW_GATEWAY_TOKEN returned nothing
  • echo $OPENCLAW_GATEWAY_TOKEN returned nothing
    Second suspect: running docker compose from repo root also is not the suspect:
  • pwd returns: /path/to/repo-root
  • ll -la .env returns: -rw-------@ 1 me staff 708B Feb 7 10:41 .env
  • grep OPENCLAW_GATEWAY_TOKEN .env returns: secret that matches ~/.openclaw/openclaw.json and /home/node/.openclaw/openclaw.json inside the gateway container
    Third suspect: the hashes match: I could not get the second command to run since the docker container is already running node- but I exec'd a sh on a running CLI container to get the hash - they match.
    Skipped 4 since the keys match but the cli is giving a token mismatch error
    5: Here is an example error from the logs: 2026-02-07T18:54:07.008Z [ws] closed before connect conn=616720f2-5b7d-44ce-a51c-31b7a118ada5 remote=192.168.65.1 fwd=n/a origin=http://localhost:18789 host=localhost:18789 ua=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 code=1008 reason=unauthorized: gateway token mismatch (open the dashboard URL and paste the token in Control UI settings)
quick hollowBOT
# open palm First suspect: shell env overriding OPEN_CLAW_GATEWAY_TOKEN: is not the case. ...

That log line is the giveaway:

  • origin=http://localhost:18789
  • close reason=unauthorized: gateway token mismatch

Even if your files and container env all agree, the Control UI does not read ~/.openclaw/openclaw.json. It reads the token from the browser (localStorage) (or from a ?token=... dashboard URL on first load). So a stale token from your previous native install can survive all your Docker resets and keep getting sent forever.

Fix (usually solves it immediately)

  1. Clear site data for the dashboard origin(s) in your browser:
  • Clear for both:
    • http://localhost:18789
    • http://127.0.0.1:18789 (different localStorage!)
  • In Chrome: Settings → Privacy & security → Site data → search “localhost” → remove

(Or just use an Incognito window as a quick test.)

#
  1. Generate a fresh tokenized dashboard URL from Docker and open that exact URL:
docker compose run --rm openclaw-cli dashboard --no-open

Open the printed link (it will include ?token=...). The UI will store it and strip it from the URL after load.

After token works, you might hit pairing

Because the gateway sees your browser as coming from 192.168.65.1 (Docker Desktop bridge), it may treat it as “remote” and require device approval.

If you then see pairing required, do:

docker compose run --rm openclaw-cli devices list
docker compose run --rm openclaw-cli devices approve <requestId>
#

If it still says token mismatch after clearing site data

Then I’d suspect the UI is still holding an old token somewhere (wrong origin / wrong browser profile) or you’re not actually opening the tokenized URL.

If you paste the output of:

docker compose run --rm openclaw-cli dashboard --no-open

(with the token redacted, just keep the host/port shape), and tell me whether you’re opening localhost or 127.0.0.1, I can tell you which storage bucket it’s using.

open palm
quick hollowBOT
# open palm docker compose run --rm openclaw-cli dashboard --no-open does not provide a toke...

openclaw dashboard should include ?token=... if either of these is set inside the container:

  • gateway.auth.token in the config it’s reading, or
  • env var OPENCLAW_GATEWAY_TOKEN

I checked the CLI implementation: it literally does:

const token = cfg.gateway?.auth?.token ?? process.env.OPENCLAW_GATEWAY_TOKEN ?? "";
const authedUrl = token ? `${httpUrl}?token=${encodeURIComponent(token)}` : httpUrl;

So if you’re seeing no token in the printed URL, that means the openclaw-cli container (for that docker compose run ...) is not seeing the token in either config or env.

1) Quick confirmation: does the CLI container see the token at all?

Run these (they don’t print the token, only length + paths):

#
docker compose run --rm openclaw-cli sh -lc 'echo "token_len=${#OPENCLAW_GATEWAY_TOKEN}"; echo "OPENCLAW_CONFIG_PATH=$OPENCLAW_CONFIG_PATH"; ls -la ~/.openclaw || true; ls -la ~/.openclaw/openclaw.json || true'
  • If token_len=0 and ~/.openclaw/openclaw.json is missing (or doesn’t have gateway.auth.token), then dashboard can’t possibly print a tokenized URL.

2) Bypass the CLI container: print the dashboard URL from inside the gateway container

This is the fastest way to get unblocked and also tells us whether the gateway container has the token configured correctly:

docker compose exec openclaw-gateway sh -lc 'node dist/index.js dashboard --no-open'

That should print a Dashboard URL: http://127.0.0.1:18789/?token=...

  • If this prints a tokenized URL: the gateway has the token, but your openclaw-cli container isn’t reading the same config/env (compose config issue: missing bind mount or missing env on the CLI service).
  • If this also prints no token: then despite appearances, the gateway process isn’t actually getting gateway.auth.token / OPENCLAW_GATEWAY_TOKEN at runtime.
#

3) About the browser “token mismatch” loop

That gateway log line:

unauthorized: gateway token mismatch ... origin=http://localhost:18789

means your browser is sending some token (almost certainly from localStorage), and it’s the wrong one.

Even after you get the correct token, make sure you either:

  • clear site data for http://localhost:18789 and http://127.0.0.1:18789, or
  • open the tokenized URL in an incognito window first (fresh storage)

4) If you paste results, I can point at the exact compose bug

Reply with the output of:

  1. the token_len=... command (step 1)
  2. the gateway-container dashboard output (step 2, redact the token part)
#

From there it’s usually: “CLI service doesn’t mount ~/.openclaw” or “CLI service doesn’t get the env/config path you think”.

open palm
#

token_len=48, OPENCLAW_CONFIG_PATH=(empty), openclaw.json contains the correct token in gateway.auth.token
Bypassing the cli container, insidethe gateway container - running docker compose exec openclaw-gateway sh -lc 'node dist/index.js dashboard --no-open' : still no tokenized URL:

Dashboard URL: http://127.0.0.1:18789/
Copy to clipboard unavailable.
No GUI detected. Open from your computer:
ssh -N -L 18789:127.0.0.1:18789 user@<host>
Then open:
http://localhost:18789/
Docs:
https://docs.openclaw.ai/gateway/remote
https://docs.openclaw.ai/web/control-ui

quick hollowBOT
# open palm token_len=48, OPENCLAW_CONFIG_PATH=(empty), openclaw.json contains the correct ...

If openclaw dashboard --no-open prints no ?token=, that’s not log redaction — in the CLI code the tokenized URL is only emitted when token is a non-empty string:

const token = cfg.gateway?.auth?.token ?? process.env.OPENCLAW_GATEWAY_TOKEN ?? "";
const authedUrl = token ? `${httpUrl}?token=${encodeURIComponent(token)}` : httpUrl;

So we need to find out why, in that exact container run, both:

  • cfg.gateway.auth.token is empty/undefined and
  • OPENCLAW_GATEWAY_TOKEN is empty/undefined

(or a subtle variant: cfg.gateway.auth.token === "" (empty string) which would “win” and prevent the env fallback because it uses ??).

1) One command that tells us what the CLI actually sees (no secrets)

Run this from the repo root on macOS:

#
docker compose run --rm openclaw-cli sh -lc '
set -e
echo "== identity =="; whoami; id; echo "HOME=$HOME"; pwd
echo
echo "== env token =="; node -e "const c=require(\"crypto\");const t=process.env.OPENCLAW_GATEWAY_TOKEN||\"\";console.log(\"env_len=\"+t.length,\"sha12=\"+c.createHash(\"sha256\").update(t).digest(\"hex\").slice(0,12))"
echo
echo "== config token (gateway.auth.token) ==";
(openclaw config get gateway.auth.token 2>/dev/null || true) \
  | node -e "const fs=require(\"fs\"),c=require(\"crypto\");const t=fs.readFileSync(0,\"utf8\").trim();console.log(\"cfg_len=\"+t.length, \"sha12=\"+(t?c.createHash(\"sha256\").update(t).digest(\"hex\").slice(0,12):\"\"))"
echo
echo "== dashboard output ==";
openclaw dashboard --no-open
'

Paste the output (it only includes lengths + 12 chars of SHA-256).

#

What we’re looking for:

  • env_len=48 is expected if the compose env is wired correctly.
  • cfg_len=48 means your ~/.openclaw/openclaw.json inside that run has the token.
  • If both are 0, that explains the plain URL immediately.
  • If env_len=48 but cfg_len=0 and dashboard is still plain → that “empty string in config blocks env fallback” case is very likely.

2) Immediate workaround (gets Control UI unstuck without waiting on the CLI)

Even if dashboard won’t print the tokenized URL, you can generate it from .env without printing the token to your terminal:

cd /path/to/openclaw-repo
token="$(grep -E '^OPENCLAW_GATEWAY_TOKEN=' .env | cut -d= -f2- | tr -d '\r\n')"
printf "http://localhost:18789/?token=%s" "$token" | pbcopy
echo "Copied tokenized URL to clipboard."
#

Then open a fresh incognito window and paste the URL.

After the token is correct, expect the next hurdle to be pairing (your gateway log showed remote=192.168.65.1, which is “remote”, not loopback). Approve it:

docker compose run --rm openclaw-cli devices list
docker compose run --rm openclaw-cli devices approve <requestId>

3) If the diagnosis shows “env has token but cfg token is empty”

The most robust fix is to store the token in config (so dashboard doesn’t depend on env at all):

docker compose run --rm openclaw-cli sh -lc '
openclaw config set gateway.auth.token "$OPENCLAW_GATEWAY_TOKEN"
'