Skip to content

openclaw status shows "unreachable (missing scope: operator.read)" for working local gateway #47307

@gamer-mitsuha

Description

@gamer-mitsuha

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:

  1. Validates the token → sharedAuthOk = true
  2. evaluateMissingDeviceIdentity() returns { kind: "allow" } (operator + sharedAuthOk)
  3. Connection is accepted ✅
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions