-
-
Notifications
You must be signed in to change notification settings - Fork 69.4k
openclaw status shows "unreachable (missing scope: operator.read)" for working local gateway #47307
Description
Summary
openclaw status reports the local gateway as unreachable (missing scope: operator.read), even though the gateway is fully functional (sessions work, channels connected, openclaw gateway status shows "Connect: ok · RPC: limited").
Environment
- OpenClaw: git HEAD @
3f434080 - OS: macOS (arm64)
- Gateway auth mode:
token(plain string configured ingateway.auth.token) - Gateway bind: loopback (
127.0.0.1)
Root Cause Analysis
Two issues combine to produce this behavior:
1. CLI probe scopes are stripped for device-less token-auth connections
In src/gateway/server/ws-connection/message-handler.ts, around L537:
if (!device && (!isControlUi || decision.kind !== "allow")) {
clearUnboundScopes();
}The CLI probe (src/gateway/probe.ts) intentionally disables device identity for loopback connections:
const disableDeviceIdentity = (() => {
try {
return isLoopbackHost(new URL(opts.url).hostname);
} catch {
return false;
}
})();
// ...
deviceIdentity: disableDeviceIdentity ? null : undefined,So the probe connects with scopes: ["operator.read"] and valid token auth. The gateway:
- Validates the token →
sharedAuthOk = true evaluateMissingDeviceIdentity()returns{ kind: "allow" }(operator + sharedAuthOk)- Connection is accepted ✅
- But
clearUnboundScopes()strips["operator.read"]→ scopes become[]❌
The subsequent RPC calls (health, status, config.get, system-presence) all require operator.read scope and fail with "missing scope: operator.read".
This was likely introduced by commit 7dc447f79 ("fix(gateway): strip unbound scopes for shared-auth connects") which correctly prevents unauthenticated clients from self-declaring scopes, but over-broadly applies to authenticated operator clients on loopback.
2. openclaw status and openclaw gateway status use different reachability checks
| Command | Check | Result |
|---|---|---|
status (status.scan.ts L234) |
gatewayProbe?.ok === true |
false (RPC failed) → "unreachable" |
gateway status (gateway-status/helpers.ts) |
isProbeReachable(probe) = ok || isScopeLimitedProbeFailure(probe) |
true → "RPC: limited" |
gateway status correctly recognizes the scope-limited state, while status treats any ok !== true as unreachable.
Expected Behavior
openclaw status should show the gateway as reachable when token auth succeeds on loopback, even without device identity. The probe should retain its declared scopes when shared auth (token/password) validates successfully.
Suggested Fix
Option A (recommended): In the scope-clearing logic, preserve scopes when sharedAuthOk === true and the evaluateMissingDeviceIdentity decision is "allow":
if (!device && (!isControlUi || decision.kind !== "allow") && !sharedAuthOk) {
clearUnboundScopes();
}Option B (incremental): Update status.scan.ts to use isProbeReachable() instead of probe.ok for consistency with gateway status:
const gatewayReachable = isProbeReachable(gatewayProbe);Option B is a workaround; Option A fixes the root cause.
Reproduction
# With gateway.auth.mode = "token" and a plain token configured:
openclaw status # Shows "unreachable (missing scope: operator.read)"
openclaw gateway status # Shows "Connect: ok (Xms) · RPC: limited - missing scope: operator.read"
# Meanwhile, actual gateway functionality (chat, channels, cron) works fine.