Summary
Internal gateway operations — cron job delivery, announce queue drain, and sessions_spawn — route through the gateway's own device credentials and fail because the paired device lacks operator.write scope. The gateway is effectively blocking itself from performing write operations.
gateway install pairs the service device using CLI_DEFAULT_OPERATOR_SCOPES ([operator.admin, operator.approvals, operator.pairing]), which is correct for CLI management. But internal runtime operations (send, poll, agent, agent.wait, announce delivery) require operator.write per METHOD_SCOPE_GROUPS. The gateway enforces device scopes on its own internal calls and rejects them.
Steps to reproduce
- Standard server install:
npm i -g openclaw && openclaw gateway install && openclaw gateway start
- Configure a cron job with Discord delivery target
- Wait for cron execution or trigger any write operation (send, announce)
- Gateway enters a scope-upgrade → "pairing required" retry loop
Expected behavior
Operations originating from the gateway process itself (cron, announce, subagent spawn) should execute without being blocked by device scope enforcement. The gateway is the authority that enforces scopes — it shouldn't need to authorize against itself.
Actual behavior
Gateway repeatedly fails with scope-upgrade rejection:
warn gateway security audit: device access upgrade requested
reason=scope-upgrade
device=bd129ab6...
scopesFrom=operator.admin,operator.approvals,operator.pairing
scopesTo=operator.write
error gateway connect failed: Error: pairing required
error gateway connect failed: Error: pairing required
error announce queue drain failed for agent:main:discord:channel:1469556013123502103:
Error: gateway closed (1008): pairing required
OpenClaw version
2026.2.19-2 (45d9b20)
Operating system
Ubuntu 24.04.3 LTS (noble), kernel 6.14.0-1018-aws, x86_64
Install method
npm i -g openclaw, systemd service via openclaw gateway install
Logs, screenshots, and evidence
Device scopes after fresh gateway install — no operator.write:
json{
"scopes": ["operator.admin", "operator.approvals", "operator.pairing"],
"tokens": {
"operator": {
"scopes": ["operator.admin", "operator.approvals", "operator.pairing"]
}
}
}
Auto-approve only handles not-paired — gateway-cli-B1gfvmQb.js:19521:
silent: isLocalClient && reason === "not-paired"
Local clients get auto-approved when they first pair (not-paired), but scope upgrades always go to the reject path — even for local/internal connections.
Note: a simple fix of extending this to reason === "not-paired" || reason === "scope-upgrade" would weaken security — an external process on the same host could silently escalate scopes. The current auto-approve for not-paired already has this gap (new device IDs get auto-approved with any scopes), but widening it further isn't ideal.
Impact and severity
Affected users: Every fresh gateway install that uses cron jobs, announce/Discord delivery, or subagent spawning — likely the majority of headless/server deployments
Severity: Blocks workflow. Cron delivery, Discord output, and subagent spawning are completely broken out of the box with no warning
Frequency: Always. 100% reproducible on any fresh install
Consequence: Missed Discord messages, silent cron failures, subagent spawn failures. Difficult to diagnose — the "pairing required" error suggests an auth/pairing problem rather than a scope omission. No documentation mentions that operator.write needs to be manually added after install
Workaround is fragile: Any subsequent gateway install --force, openclaw update + doctor --repair, or backup restore will re-create the paired device with default scopes, silently reverting the fix
Additional information
suggested fix: In src/gateway/server/ws-connection/message-handler.ts, skip device scope enforcement for internal gateway-client connections (same pattern as the existing allowControlUiBypass):
// Current:
if (device && devicePublicKey && !(allowControlUiBypass && sharedAuthOk)) {
// Fixed:
const isInternalGatewayClient = isLocalClient && connectParams.client.id === "gateway-client";
if (device && devicePublicKey && !(allowControlUiBypass && sharedAuthOk) && !isInternalGatewayClient) {
So when the gateway's own cron/announce/subagent connects (client.id === "gateway-client" + isLocalClient), the condition becomes false → skips the entire scope check block → connection proceeds without checking paired.json scopes at all → no "scope-upgrade" → no "pairing required".
Workaround
openclaw devices rotate --device <id> --role operator \
--scope operator.admin \
--scope operator.approvals \
--scope operator.pairing \
--scope operator.write
openclaw gateway restart
Device ID via openclaw devices list. If gateway is down, manually add operator.write to both scope arrays in ~/.openclaw/devices/paired.json.
Related issues:
Summary
Internal gateway operations — cron job delivery, announce queue drain, and
sessions_spawn— route through the gateway's own device credentials and fail because the paired device lacksoperator.writescope. The gateway is effectively blocking itself from performing write operations.gateway installpairs the service device usingCLI_DEFAULT_OPERATOR_SCOPES([operator.admin, operator.approvals, operator.pairing]), which is correct for CLI management. But internal runtime operations (send, poll, agent, agent.wait, announce delivery) requireoperator.writeperMETHOD_SCOPE_GROUPS. The gateway enforces device scopes on its own internal calls and rejects them.Steps to reproduce
npm i -g openclaw && openclaw gateway install && openclaw gateway startExpected behavior
Operations originating from the gateway process itself (cron, announce, subagent spawn) should execute without being blocked by device scope enforcement. The gateway is the authority that enforces scopes — it shouldn't need to authorize against itself.
Actual behavior
Gateway repeatedly fails with scope-upgrade rejection:
OpenClaw version
2026.2.19-2 (45d9b20)
Operating system
Ubuntu 24.04.3 LTS (noble), kernel 6.14.0-1018-aws, x86_64
Install method
npm i -g openclaw, systemd service via openclaw gateway install
Logs, screenshots, and evidence
Device scopes after fresh gateway install — no operator.write: json{ "scopes": ["operator.admin", "operator.approvals", "operator.pairing"], "tokens": { "operator": { "scopes": ["operator.admin", "operator.approvals", "operator.pairing"] } } } Auto-approve only handles not-paired — gateway-cli-B1gfvmQb.js:19521: silent: isLocalClient && reason === "not-paired" Local clients get auto-approved when they first pair (not-paired), but scope upgrades always go to the reject path — even for local/internal connections. Note: a simple fix of extending this to reason === "not-paired" || reason === "scope-upgrade" would weaken security — an external process on the same host could silently escalate scopes. The current auto-approve for not-paired already has this gap (new device IDs get auto-approved with any scopes), but widening it further isn't ideal.Impact and severity
Affected users: Every fresh gateway install that uses cron jobs, announce/Discord delivery, or subagent spawning — likely the majority of headless/server deployments
Severity: Blocks workflow. Cron delivery, Discord output, and subagent spawning are completely broken out of the box with no warning
Frequency: Always. 100% reproducible on any fresh install
Consequence: Missed Discord messages, silent cron failures, subagent spawn failures. Difficult to diagnose — the "pairing required" error suggests an auth/pairing problem rather than a scope omission. No documentation mentions that operator.write needs to be manually added after install
Workaround is fragile: Any subsequent gateway install --force, openclaw update + doctor --repair, or backup restore will re-create the paired device with default scopes, silently reverting the fix
Additional information
suggested fix: In
src/gateway/server/ws-connection/message-handler.ts, skip device scope enforcement for internal gateway-client connections (same pattern as the existingallowControlUiBypass):Workaround
Device ID via
openclaw devices list. If gateway is down, manually addoperator.writeto both scope arrays in~/.openclaw/devices/paired.json.Related issues:
openclaw node runfails silently with "1008: pairing required" when connecting to a remote gateway #4833 — node run fails with "pairing required"