#scope upgrade pending approval blocks all browser commands

1 messages · Page 1 of 1 (latest)

keen kindle
#

OpenClaw v2026.5.3-1, Hostinger VPS, Ubuntu, Node v22.22.2

All browser CLI commands fail with:
scope upgrade pending approval (requestId: d0ec96f4-7beb-45a6-825d-10ca6c1edbcb)
gateway closed (1008): pairing required: device is asking for more scopes than currently approved

Commands tried: browser start, browser status, browser doctor
Gateway and Telegram channels work fine. Chromium installed and works manually.

How to approve browser scope on Hostinger-hosted instance? No approval UI visible.

mossy fernBOT
#

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

molten tokenBOT
#

This is device pairing scope-upgrade, not Chromium itself. On a headless/VPS install you approve it from SSH with the devices CLI — no approval UI required.

Run on the Hostinger VPS as the same user that runs OpenClaw:

openclaw devices list
openclaw devices approve --latest   # preview only; review Requested vs Approved
openclaw devices approve d0ec96f4-7beb-45a6-825d-10ca6c1edbcb
openclaw browser doctor

A couple of gotchas:

  • Run openclaw devices list right before approving; if the browser command retried, it may have superseded the old requestId with a new one.
  • For the local VPS approval path, don’t pass --url unless you also pass credentials with enough pairing/admin scope. Without an explicit --url, openclaw devices can use the local pairing fallback for loopback when the current CLI token is exactly what needs the scope upgrade.
  • Use openclaw devices approve ..., not openclaw pairing approve ... and not openclaw nodes approve ... — those are different pairing flows.

Docs: https://docs.openclaw.ai/cli/devices and https://docs.openclaw.ai/web/control-ui#device-pairing-first-connection 🦞

keen kindle
#

Ran openclaw devices approve 24cc4160-6863-4938-906b-24c575bd04ae but it generated new requestIds (9a24c7a0... then 3851e34a...) and scope upgrade loop continues. Each approve attempt triggers a new pending request. browser doctor still fails. How to break this loop?

molten tokenBOT
#

Yep — that loop usually means the approval command itself is connecting with the stale/low-scope paired device token, so every devices approve attempt creates/refreshes a scope-upgrade request for the CLI instead of cleanly approving the browser one.

Break it by bypassing the stored device token and approving with the shared gateway token/password from the VPS itself.

Try this on the VPS, as the same Linux user that runs OpenClaw:

# 1) Back up the cached paired-device token used by the CLI
mkdir -p ~/.openclaw/identity
cp -a ~/.openclaw/identity/device-auth.json ~/.openclaw/identity/device-auth.json.bak.$(date +%s) 2>/dev/null || true
mv ~/.openclaw/identity/device-auth.json ~/.openclaw/identity/device-auth.json.disabled 2>/dev/null || true

# 2) Use the shared gateway token explicitly. Do not paste this token in Discord.
TOKEN="$(openclaw config get gateway.auth.token)"

# 3) List pending requests through local loopback with explicit shared auth
#
openclaw devices list --url ws://127.0.0.1:18789 --token "$TOKEN"

# 4) Approve the CURRENT latest request shown by the list command
openclaw devices approve <CURRENT_REQUEST_ID> --url ws://127.0.0.1:18789 --token "$TOKEN"

# 5) Retry browser with the same explicit auth once
openclaw browser doctor --url ws://127.0.0.1:18789 --token "$TOKEN"

If your gateway is in password mode instead of token mode, replace --token "$TOKEN" with --password 'your-gateway-password'.

Important: don’t use one of the old request IDs. Run devices list after disabling device-auth.json, then approve the request ID it shows right then.

If that fixes it, future commands should usually be okay. If they still try the stale auth path, export the shared token in that SSH shell before running browser commands:

keen kindle
#

Update: Approving with gateway token works (device gets approved) but each new CLI command (browser doctor) generates a fresh scope upgrade request with a new requestId. Approved 15b5029a then got 3fd832ed, approved that then got e88e437d. Infinite loop. device-auth.json already disabled. Using export OPENCLAW_GATEWAY_TOKEN with the actual gateway token. How to break this cycle and grant full operator scopes permanently?

molten tokenBOT
#

That pattern means this is no longer “needs one more approve” — the Gateway is seeing each browser doctor connection as a new/changed device identity or as a device whose cached token/scopes can’t persist. You don’t “grant full operator scopes globally”; OpenClaw grants them to a stable device id + public key.

First check whether the device id is changing:

TOKEN="$(openclaw config get gateway.auth.token)"

jq -r '.deviceId' ~/.openclaw/identity/device.json
openclaw devices list --url ws://127.0.0.1:18789 --token "$TOKEN" --json \
  | jq '.pending[]? | {requestId, deviceId, clientId, clientMode, scopes}'

If the deviceId in pending changes every run, fix the identity file. This is the usual loop-breaker:

# run as the SAME user that runs `openclaw browser doctor`
#
ls -la ~/.openclaw/identity
stat ~/.openclaw/identity/device.json

# fix ownership/perms; replace $USER if needed
sudo chown -R "$USER:$USER" ~/.openclaw/identity
chmod 700 ~/.openclaw/identity
chmod 600 ~/.openclaw/identity/device.json 2>/dev/null || true

# make sure the identity is stable across invocations
jq -r '.deviceId' ~/.openclaw/identity/device.json
jq -r '.deviceId' ~/.openclaw/identity/device.json

Then stop disabling device-auth.json — that file is where the approved device token/scopes get cached after a successful connect.

Do the clean approval once:

#
TOKEN="$(openclaw config get gateway.auth.token)"

# trigger one fresh request
OPENCLAW_GATEWAY_TOKEN="$TOKEN" openclaw browser doctor || true

# approve the CURRENT request id, not an older one
openclaw devices list --url ws://127.0.0.1:18789 --token "$TOKEN"
openclaw devices approve <CURRENT_REQUEST_ID> --url ws://127.0.0.1:18789 --token "$TOKEN"

# now let it connect and write ~/.openclaw/identity/device-auth.json
OPENCLAW_GATEWAY_TOKEN="$TOKEN" openclaw browser doctor

# verify the cached token now has broad operator scopes
jq '.tokens.operator.scopes' ~/.openclaw/identity/device-auth.json
#

Expected cached scopes should include at least operator.admin or the full set (operator.read, operator.write, operator.pairing, operator.approvals, etc.).

If deviceId is stable but this still creates a new request every time, please paste the redacted output of:

openclaw devices list --url ws://127.0.0.1:18789 --token "$TOKEN"
ls -la ~/.openclaw/identity
jq '{deviceId, tokens: (.tokens | keys)}' ~/.openclaw/identity/device-auth.json 2>/dev/null

Don’t paste the token or full device-auth.json contents. If stable identity + writable device-auth.json still loops on 2026.5.3-1, that smells like a pairing/cache regression and should go to GitHub: https://github.com/openclaw/openclaw/issues

keen kindle
#

Update: device identity stable (629df15c...), approval works but device-auth.json never caches the upgraded scopes. Each new CLI command creates fresh scope upgrade request. Approved 5+ requests, same loop. device-auth.json exists, permissions 600, owned by root. Paired scopes stay at operator.read, operator.pairing even after approving requests for operator.admin, operator.write etc. What is preventing scope persistence?

molten tokenBOT
#

That last detail is the smoking gun: device-auth.json is owned by root.

The upgraded token/scopes are returned by the Gateway on successful connect, then the CLI tries to write them to:

~/.openclaw/identity/device-auth.json

If that file is 600 root:root and you run openclaw browser doctor as a normal user, OpenClaw cannot replace the cached low-scope token. So every new CLI process loads/keeps the old operator.read, operator.pairing view and asks for another upgrade. 🦞

Fix the state ownership, then remove the stale cache and approve once more:

# As the Linux user you normally run OpenClaw CLI as — NOT via sudo
whoami
printf 'HOME=%s
#
' "$HOME"

# Fix the OpenClaw state dir ownership
sudo chown -R "$USER:$USER" "$HOME/.openclaw"
chmod 700 "$HOME/.openclaw" "$HOME/.openclaw/identity"
chmod 600 "$HOME/.openclaw/identity/device.json" 2>/dev/null || true

# Remove the stale low-scope cached token only; keep device.json
mv "$HOME/.openclaw/identity/device-auth.json" \
   "$HOME/.openclaw/identity/device-auth.json.stale.$(date +%s)" 2>/dev/null || true

TOKEN="$(openclaw config get gateway.auth.token)"

# Trigger one fresh upgrade request
OPENCLAW_GATEWAY_TOKEN="$TOKEN" openclaw browser doctor || true
#

# Approve the CURRENT request
openclaw devices list --url ws://127.0.0.1:18789 --token "$TOKEN"
openclaw devices approve <CURRENT_REQUEST_ID> --url ws://127.0.0.1:18789 --token "$TOKEN"

# Connect again so the upgraded device token can be cached
OPENCLAW_GATEWAY_TOKEN="$TOKEN" openclaw browser doctor

# Verify local cache now persisted upgraded scopes
ls -l "$HOME/.openclaw/identity/device-auth.json"
jq '.tokens.operator.scopes' "$HOME/.openclaw/identity/device-auth.json"

Also check you’re not mixing sudo openclaw ... and non-sudo openclaw .... Pick one OS user and stick to it. Prefer the normal user; don’t run browser CLI with sudo.

If your gateway service itself is running as root while your CLI is running as another user, that can create two OpenClaw state worlds (/root/.openclaw vs /home/<user>/.openclaw). Check with:

#
openclaw gateway status

If it shows the service/state is under root, reinstall/run the gateway service as the normal user, or run all commands consistently as that same user. Mixed ownership/state is exactly how this loop happens.

keen kindle
#

Found the issue: device-auth.json exists but scopes only show ["operator.pairing"]. Even after approving scope upgrade requests multiple times, the file never updates to include operator.admin, operator.write, operator.approvals etc. File is 600 root:root, identity dir is 700 root:root, all commands run as root. The cached token scopes stay at operator.pairing and never upgrade. Is this a bug in scope persistence on v2026.5.3-1?

device-auth.json content:

  • deviceId: 629df15c... (stable)
  • scopes: ["operator.pairing"] (never upgrades)
  • updatedAtMs: 1778713227046
molten tokenBOT
#

Yes — with those facts, this looks like the known bad state / likely bug class: the device is stable, but the cached operator device token remains narrowed to operator.pairing, so every fresh CLI process reconnects with that narrow cached scope and triggers another upgrade.

One important nuance: openclaw devices approve ... updates the Gateway’s paired-device record. It does not directly edit ~/.openclaw/identity/device-auth.json; that file is only refreshed after a successful client connect receives a new hello-ok device token.

First, confirm whether approval is actually broadening the Gateway-side token:

TOKEN="$(openclaw config get gateway.auth.token)"
REQ=<current_request_id>

openclaw devices approve "$REQ" \
  --url ws://127.0.0.1:18789 \
  --token "$TOKEN" \
  --json | jq '{approvedScopes: .device.approvedScopes, tokenScopes: .device.tokens.operator.scopes}'

If tokenScopes is still only operator.pairing, then the approve path is not persisting the requested scopes — that’s a bug/regression. Please file it with that JSON output: https://github.com/openclaw/openclaw/issues

#

Workaround to force the Gateway-side operator token broad:

DEVICE=629df15c...   # full device id
TOKEN="$(openclaw config get gateway.auth.token)"

openclaw devices rotate \
  --device "$DEVICE" \
  --role operator \
  --scope operator.admin \
  --scope operator.read \
  --scope operator.write \
  --scope operator.approvals \
  --scope operator.pairing \
  --scope operator.talk.secrets \
  --url ws://127.0.0.1:18789 \
#
  --token "$TOKEN" \
  --json

# remove stale local cache; keep device.json
mv ~/.openclaw/identity/device-auth.json ~/.openclaw/identity/device-auth.json.stale.$(date +%s)

# force explicit shared-token auth for the next successful connect/cache write
openclaw browser doctor --url ws://127.0.0.1:18789 --token "$TOKEN"

jq '.tokens.operator.scopes' ~/.openclaw/identity/device-auth.json

If that still writes only operator.pairing, then it’s definitely not user error — the server/client is returning/caching the wrong token scopes on v2026.5.3-1. Include:

#
openclaw --version
openclaw devices list --url ws://127.0.0.1:18789 --token "$TOKEN" --json | jq '.paired[] | select(.deviceId|startswith("629df15c")) | {scopes, tokens}'
jq '{deviceId, scopes: .tokens.operator.scopes, updatedAtMs: .tokens.operator.updatedAtMs}' ~/.openclaw/identity/device-auth.json

Don’t paste tokens themselves.

keen kindle
#

devices rotate returned "device token rotation denied". Manually editing device-auth.json scopes also doesn't work because Gateway validates scopes server-side. Paired device scopes on Gateway remain operator.read, operator.pairing even after multiple successful approvals. approve command succeeds but Gateway never broadens the paired device scopes. This appears to be a server-side bug in scope upgrade persistence on v2026.5.3-1. Need dev help.

molten tokenBOT
#

Agreed — at this point this is very likely server-side scope-upgrade persistence bug, not a Hostinger/Chromium problem.

device token rotation denied also fits: rotation is bounded by the Gateway’s paired-device approved scope baseline. Since the Gateway baseline is stuck at operator.read, operator.pairing, it refuses to mint operator.admin/write/approvals. Manual device-auth.json edits won’t work either because the server validates against that same baseline.

Immediate workaround worth trying: delete this paired device record and re-pair fresh, instead of upgrading the stuck record.

TOKEN="$(openclaw config get gateway.auth.token)"
DEVICE="629df15c..."   # full device id

# Remove the stuck server-side pairing record
openclaw devices remove "$DEVICE" \
  --url ws://127.0.0.1:18789 \
  --token "$TOKEN"

# Remove stale local cached token, keep device.json
#
mv ~/.openclaw/identity/device-auth.json \
   ~/.openclaw/identity/device-auth.json.stale.$(date +%s) 2>/dev/null || true

# Trigger a clean first-time pairing request
OPENCLAW_GATEWAY_TOKEN="$TOKEN" openclaw browser doctor || true

# Approve the CURRENT request id shown here
openclaw devices list --url ws://127.0.0.1:18789 --token "$TOKEN"
openclaw devices approve <CURRENT_REQUEST_ID> \
  --url ws://127.0.0.1:18789 \
  --token "$TOKEN" \
  --json | jq '{approvedScopes: .device.approvedScopes, tokenScopes: .device.tokens.operator.scopes}'

# Retry and verify cache
OPENCLAW_GATEWAY_TOKEN="$TOKEN" openclaw browser doctor
#
jq '.tokens.operator.scopes' ~/.openclaw/identity/device-auth.json

If the fresh pairing also comes back with only operator.read, operator.pairing, please file this as a bug here: https://github.com/openclaw/openclaw/issues

Suggested title:
v2026.5.3-1 device scope-upgrade approval succeeds but paired approvedScopes never broaden

Include:

  • openclaw --version
  • OS/Node: Ubuntu VPS, Node v22.22.2
  • Redacted openclaw devices list --json for the affected device
  • The approve --json | jq '{approvedScopes, tokenScopes}' output
  • Note that rotate returns device token rotation denied
  • Note that server-side paired scopes remain operator.read, operator.pairing after successful approvals
#

That’s enough for devs to reproduce without any secrets.

keen kindle
#

Browser scope issue fixed (device remove + fresh pairing worked). Browser doctor now shows all OK. Chromium starts and CDP works on port 18800. But agent still shows "Missing API key for the selected provider on the gateway" when trying to use browser tool via Telegram. Model is openrouter/google/gemini-2.5-flash, auth profile openrouter:default exists in openclaw.json with valid key. Text responses from agent work fine, only browser tool calls fail with this error. Is there a separate auth config needed for browser tool calls?

molten tokenBOT
#

Nice, glad the browser pairing is finally unstuck 🎉

No — the browser tool does not need a separate model/API-key config. That error is from the agent LLM run, not Chromium/CDP.

The likely causes are:

  1. Telegram is routed to a different agent store than the one where openrouter:default exists. Auth profiles are per-agent, not really global.
  2. The Telegram session is pinned to a stale/missing auth profile.
  3. The key exists as config metadata/order, but not in the actual agent auth store.

Check the active Telegram agent + auth store:

openclaw agents list --bindings
openclaw models auth list --provider openrouter --json
# if Telegram is bound to a non-main agent:
#
openclaw models auth --agent <telegram-agent-id> list --provider openrouter --json
openclaw models status --json

In the Telegram chat, also run:

/model status

That should show the active agent and which auth-profiles.json path/profile is being used.

To force the session onto the right model/profile: