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 in gateway.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.
Summary
openclaw statusreports the local gateway asunreachable (missing scope: operator.read), even though the gateway is fully functional (sessions work, channels connected,openclaw gateway statusshows "Connect: ok · RPC: limited").Environment
3f434080token(plain string configured ingateway.auth.token)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:The CLI probe (
src/gateway/probe.ts) intentionally disables device identity for loopback connections:So the probe connects with
scopes: ["operator.read"]and valid token auth. The gateway:sharedAuthOk = trueevaluateMissingDeviceIdentity()returns{ kind: "allow" }(operator + sharedAuthOk)clearUnboundScopes()strips["operator.read"]→ scopes become[]❌The subsequent RPC calls (
health,status,config.get,system-presence) all requireoperator.readscope 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 statusandopenclaw gateway statususe different reachability checksstatus(status.scan.tsL234)gatewayProbe?.ok === truefalse(RPC failed) → "unreachable"gateway status(gateway-status/helpers.ts)isProbeReachable(probe)=ok || isScopeLimitedProbeFailure(probe)true→ "RPC: limited"gateway statuscorrectly recognizes the scope-limited state, whilestatustreats anyok !== trueas unreachable.Expected Behavior
openclaw statusshould 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 === trueand theevaluateMissingDeviceIdentitydecision is"allow":Option B (incremental): Update
status.scan.tsto useisProbeReachable()instead ofprobe.okfor consistency withgateway status:Option B is a workaround; Option A fixes the root cause.
Reproduction