Skip to content

Gateway WS clients with token-auth cannot subscribe to channel message events (granted scopes = []) #81775

Description

@vivekchand

Symptom

When a third-party tool (in our case ClawMetry's sync daemon) connects to the gateway over ws://127.0.0.1:18789 using the token from gateway.auth.token, the connect handshake succeeds but payload.auth.scopes comes back empty, regardless of what the client puts in params.scopes. Every method that requires a scope is then rejected:

sessions.messages.subscribe -> ok=False  err="missing scope: operator.read"
sessions.subscribe          -> ok=False  err="missing scope: operator.read"
logs.tail                   -> ok=False  err="missing scope: operator.read"
channels.status             -> ok=False  err="missing scope: operator.read"
node.event                  -> ok=False  err="unauthorized role: operator"

Only health (which the gateway also pushes unsolicited) responds. As a result a WS client cannot subscribe to per-session message events at all — which is the only path to capture inbound Telegram / Signal / WhatsApp / Discord / … message bodies, since those adapters store conversations entirely in memory (no JSONL on disk; gateway.log only carries outbound sendMessage ACKs without bodies).

Repro

~/.openclaw/openclaw.json excerpt (typical fresh install):

{
  "gateway": {
    "mode": "local",
    "auth": { "mode": "token", "token": "<48-char>" },
    "port": 18789,
    "bind": "loopback"
  }
}
import json, uuid, websocket
ws = websocket.create_connection("ws://127.0.0.1:18789/", timeout=5)
ws.recv()  # connect.challenge
cid = "probe-" + uuid.uuid4().hex[:8]
ws.send(json.dumps({
    "type": "req", "id": cid, "method": "connect",
    "params": {
        "minProtocol": 3, "maxProtocol": 3,
        "client": {"id": "cli", "version": "0.0.0", "platform": "darwin",
                   "mode": "cli", "instanceId": cid},
        "role": "operator",
        "scopes": ["operator.admin", "operator.read"],
        "auth": {"token": "<token-from-config>"},
    },
}))
r = json.loads(ws.recv())
print(r["payload"]["auth"])      # {"role": "operator", "scopes": []}
# Any sessions.messages.subscribe / logs.tail / channels.status now 401s.

OpenClaw 2026.5.7 (server.version from the connect-ok payload).

Why this is a P0 for downstream observability

Without WS subscribe access, third-party dashboards have no way to see inbound chat-channel messages at all. The only surviving signal is gateway.log outbound ACK lines (no body), which means the user's actual conversations (the most important thing to observe) are invisible to every tool that isn't OpenClaw itself.

Request

Either:

  1. Land RFE: gateway.auth.tokenScopes config field for headless/single-tenant deployments #80836 (gateway.auth.tokenScopes config field) so headless / single-tenant deployments can declare the token's effective scopes statically. Our use case fits RFE: gateway.auth.tokenScopes config field for headless/single-tenant deployments #80836 exactly — we'd set tokenScopes: ["operator.read"] and the WS subscribe path would just work.

  2. Or document a recommended way for a loopback-bound, token-authed CLI client to obtain operator.read non-interactively, so an observability daemon can subscribe to message events without forcing the user through the device-pairing approval UI on every install.

Either resolution unblocks ClawMetry's live WS tap (PR https://github.com/vivekchand/clawmetry/pull/) and any other observer that wants to see in-memory channel traffic.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions