openclaw - ✅(Solved) Fix [Bug]: Device pairing has no bypass for Docker CLI or internal callGateway() operations [1 pull requests, 1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#55067Fetched 2026-04-08 01:32:54
View on GitHub
Comments
1
Participants
1
Timeline
16
Reactions
0
Author
Participants
Timeline (top)
referenced ×10labeled ×2closed ×1commented ×1

In Docker deployments, the gateway's device pairing system blocks all CLI commands and intermittently blocks internal callGateway() operations (cron, sessions_spawn, sessions_list) with "pairing required" (WebSocket close code 1008), despite both containers sharing the same identity file, auth token, and network namespace.

Error Message

gateway connect failed: GatewayClientRequestError: pairing required Error: gateway closed (1008): pairing required Gateway target: ws://127.0.0.1:18789 Source: local loopback Config: /home/node/.openclaw/openclaw.json Bind: loopback

Root Cause

  • Affected: All Docker-based OpenClaw deployments using CLI commands or relying on internal callGateway() operations (cron tools, sessions_spawn, sessions_list). At least 12 prior issues report the same root cause (#2284, #4531, #4941, #6085, #6959, #9028, #12210, #16204, #19352, #20707, #23471, #30740).
  • Severity: Blocks workflow. CLI is completely unusable. Internal operations intermittently fail.
  • Frequency: 100% for CLI commands. Intermittent for internal callGateway() (cron, sessions_spawn).
  • Consequence: No CLI access for config management, device administration, cron inspection, or diagnostics. Cron jobs that use sessions_spawn or sessions_list fail silently. Users are forced to rely solely on Web UI or abandon Docker for native installs.

PR fix notes

PR #55113: FIX (gateway): skip device pairing for authenticated CLI connections in Docker

Description (problem / solution / changelog)

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: Docker CLI commands fail with pairing required (1008) because shouldSkipBackendSelfPairing only matches gateway-client/backend mode. CLI connections use cli/cli mode and are excluded.
  • Problem (secondary): isLocalDirectRequest returns false in Docker even when remoteAddress is 127.0.0.1, so the locality check fails as a second blocker.
  • Why it matters: Every Docker deployment using CLI commands hits this. At least 12 issues report the same root cause (#2284, #4531, #4941, #6959, #9028, #12210, #19352, #20707, #23471, #30740, #42931, #45232). Users are forced to abandon Docker or lose CLI access entirely.
  • What changed: handshake-auth-helpers.ts — extend shouldSkipBackendSelfPairing to match cli/cli clients. For CLI connections with valid shared auth (token/password), skip the locality check since the token is the trust anchor.
  • What did NOT change (scope boundary): Pairing behavior for external clients (Control UI, mobile apps, nodes); backend self-connection locality requirement; auth validation logic; TLS checks.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #55067
  • Related #12210, #23471, #30740, #30801
  • This PR fixes a bug or regression

Root Cause / Regression History (if applicable)

  • Root cause: shouldSkipBackendSelfPairing gates on GATEWAY_CLIENT_IDS.GATEWAY_CLIENT + GATEWAY_CLIENT_MODES.BACKEND. CLI connections send GATEWAY_CLIENT_IDS.CLI + GATEWAY_CLIENT_MODES.CLI and are excluded from the bypass. Even if the client ID check is broadened, isLocalDirectRequest() returns false in Docker host networking despite remoteAddress being 127.0.0.1 — a secondary false-negative in the locality detection.
  • Missing detection / guardrail: No test coverage for CLI-mode pairing bypass. No Docker-specific integration test for CLI→gateway WebSocket connectivity.
  • Prior context: PR #30801 (merged March 2) added isBackendSelfConnection to broaden the bypass for TLS+subagent scenarios (#30740), but (a) the fix only covered gateway-client/backend mode, not cli/cli, and (b) the isBackendSelfConnection identifier is absent from built output on both v2026.3.23 and main at 2026.3.24-beta.1 — suggesting it was reverted or overwritten.
  • Why this regressed now: This never worked — it's a design gap, not a regression. The pairing system was built for remote mobile/external clients and never accommodated co-located Docker containers.
  • If unknown, what was ruled out: N/A — root cause is confirmed via debug instrumentation.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/gateway/server/ws-connection/handshake-auth-helpers.test.ts
  • Scenario the test should lock in: shouldSkipBackendSelfPairing returns true for cli/cli client with isLocalClient: true, sharedAuthOk: true, authMethod: "token". Returns false when remote or when auth fails.
  • Why this is the smallest reliable guardrail: Unit test directly exercises the bypass function with CLI-mode inputs — catches any future narrowing of the client ID/mode match.
  • Existing test that already covers this (if any): Existing tests only cover gateway-client/backend mode.
  • If no new test is added, why not: New test IS added — three cases for CLI mode (local+auth passes, remote fails, no-auth fails).

User-visible / Behavior Changes

  • docker compose run --rm openclaw-cli <command> now works in Docker deployments
  • docker exec openclaw-openclaw-cli-1 openclaw <command> now works
  • Internal callGateway() operations from CLI-mode clients no longer fail with pairing required

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation: The CLI pairing bypass is gated on: (1) client ID is cli, (2) client mode is cli, (3) shared auth succeeded, (4) no browser origin header. A CLI connection with a valid token already has full operator access — skipping pairing does not expand the access surface. Backend self-connections retain the existing locality check as defense-in-depth.

Repro + Verification

Environment

  • OS: Ubuntu (headless)
  • Runtime/container: Docker Engine + Compose v2, host networking, network_mode: "service:openclaw-gateway" for CLI
  • Model/provider: Not model-specific (moonshot/kimi-k2.5 in test)
  • Integration/channel (if any): N/A
  • Relevant config (redacted): gateway.auth.mode: "token", gateway.bind: "loopback", standard docker-compose.yml from repo

Steps

  1. Deploy with Docker Compose (gateway + CLI containers sharing network namespace)
  2. Run: docker compose run --rm --entrypoint /usr/local/bin/openclaw openclaw-cli cron list
  3. Observe result

Expected

  • Cron job list displayed

Actual

  • Before fix: gateway closed (1008): pairing required
  • After fix: 14 cron jobs listed successfully

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets

Before:

gateway connect failed: GatewayClientRequestError: pairing required
Error: gateway closed (1008): pairing required
Gateway target: ws://127.0.0.1:18789
Source: local loopback

Debug instrumentation showing isLocalClient: false despite remoteAddress: "127.0.0.1":

{"isLocalClient":false,"remoteAddress":"127.0.0.1","hostHeader":"127.0.0.1:18789","xForwardedFor":null,"xRealIp":null,"xForwardedHost":null}

Debug instrumentation showing bypass function receiving correct values but blocked by isLocalClient:

{"clientId":"cli","clientMode":"cli","isLocalTrustedClient":true,"isLocalClient":false,"sharedAuthOk":true,"authMethod":"token"}

After fix: CLI returns full cron job listing (14 jobs).

Human Verification (required)

  • Verified scenarios: CLI cron list works in Docker deployment after fix. Gateway starts cleanly. Web UI still works. Telegram/Discord bots unaffected.
  • Edge cases checked: Backend self-connections unaffected (retain locality check); failed auth does not skip pairing; browser-origin header blocks bypass.
  • What you did not verify: Non-Docker native installs (no access to test environment), TLS configurations, mobile app pairing.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: Revert the isLocalTrustedClient and isCli changes in handshake-auth-helpers.ts, restoring the original isGatewayBackendClient check.
  • Files/config to restore: src/gateway/server/ws-connection/handshake-auth-helpers.ts
  • Known bad symptoms reviewers should watch for: Unauthorized CLI connections bypassing pairing from non-localhost origins (would require a valid gateway token, so low risk).

Risks and Mitigations

  • Risk: A process on a remote host with a stolen gateway token could impersonate a CLI client and bypass pairing.
    • Mitigation: The token already grants full operator access. An attacker with the token can use the Web UI (which already bypasses pairing via dangerouslyDisableDeviceAuth). Skipping pairing for CLI does not expand the attack surface.
  • Risk: isLocalDirectRequest false negative in Docker may indicate a deeper networking issue worth a separate fix.
    • Mitigation: Filed as a note in the issue. This PR works around it for CLI; a separate investigation into isLocalDirectRequest Docker behavior is recommended.

Changed files

  • CHANGELOG.md (modified, +3/-0)
  • src/gateway/server.auth.control-ui.suite.ts (modified, +79/-0)
  • src/gateway/server/ws-connection/handshake-auth-helpers.test.ts (modified, +167/-1)
  • src/gateway/server/ws-connection/handshake-auth-helpers.ts (modified, +48/-1)
  • src/gateway/server/ws-connection/message-handler.ts (modified, +28/-7)

Code Example

gateway connect failed: GatewayClientRequestError: pairing required
Error: gateway closed (1008): pairing required
Gateway target: ws://127.0.0.1:18789
Source: local loopback
Config: /home/node/.openclaw/openclaw.json
Bind: loopback

---

$ docker compose run --rm --entrypoint /usr/local/bin/openclaw openclaw-cli cron list
gateway connect failed: GatewayClientRequestError: pairing required
Error: gateway closed (1008): pairing required
Gateway target: ws://127.0.0.1:18789
Source: local loopback
Config: /home/node/.openclaw/openclaw.json
Bind: loopback

---

[tools] cron failed: gateway closed (1008): pairing required
[tools] sessions_list failed: gateway closed (1008): pairing required
[tools] gateway failed: gateway closed (1008): pairing required

---

$ docker exec openclaw-openclaw-cli-1 cat /home/node/.openclaw/identity/device.json | grep deviceId
  "deviceId": "9856fe882d40ddd78449edbdf7c64411122464ed6766364d02a3b8abdbd6b6e1"
$ docker exec openclaw-openclaw-gateway-1 cat /home/node/.openclaw/identity/device.json | grep deviceId
  "deviceId": "9856fe882d40ddd78449edbdf7c64411122464ed6766364d02a3b8abdbd6b6e1"

---

function shouldSkipBackendSelfPairing(params) {
    if (!(params.connectParams.client.id === GATEWAY_CLIENT_IDS.GATEWAY_CLIENT 
        && params.connectParams.client.mode === GATEWAY_CLIENT_MODES.BACKEND)) return false;
    // ...
}
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Summary

In Docker deployments, the gateway's device pairing system blocks all CLI commands and intermittently blocks internal callGateway() operations (cron, sessions_spawn, sessions_list) with "pairing required" (WebSocket close code 1008), despite both containers sharing the same identity file, auth token, and network namespace.

Steps to reproduce

  1. Build from tag v2026.3.23: git fetch origin --tags && git checkout refs/tags/v2026.3.23 && docker build -t openclaw:2026.3.23 .
  2. Deploy with Docker Compose using network_mode: "service:openclaw-gateway" for the CLI container (standard docker-compose.yml from repo)
  3. Confirm gateway is healthy: Web UI works via http://127.0.0.1:18789/?token=<token>, Telegram/Discord bots connect
  4. Run any CLI command: docker compose run --rm --entrypoint /usr/local/bin/openclaw openclaw-cli cron list

Expected behavior

CLI commands should succeed when the CLI container shares the gateway's network namespace (network_mode: "service:openclaw-gateway"), reads the same identity/device.json, and has access to the same gateway auth token. The Docker install docs show CLI commands working via docker compose run --rm openclaw-cli <command>.

Actual behavior

Every CLI command fails immediately:

gateway connect failed: GatewayClientRequestError: pairing required
Error: gateway closed (1008): pairing required
Gateway target: ws://127.0.0.1:18789
Source: local loopback
Config: /home/node/.openclaw/openclaw.json
Bind: loopback

Gateway log shows: [ws] closed before connect conn=<id> remote=127.0.0.1 fwd=n/a origin=n/a host=127.0.0.1:18789 ua=n/a code=1008 reason=pairing required

No pending device request is created in devices/pending.json — the connection is rejected before requestDevicePairing is reached. Internal callGateway() operations (cron jobs, sessions_spawn, sessions_list) also intermittently fail with the same error.

shouldSkipBackendSelfPairing only matches GATEWAY_CLIENT_IDS.GATEWAY_CLIENT + GATEWAY_CLIENT_MODES.BACKEND. CLI connections use GATEWAY_CLIENT_MODES.CLI and are not covered.

PR #30801 (merged March 2) added isBackendSelfConnection to broaden the bypass, but this identifier is absent from the built output on both v2026.3.23 and main (2026.3.24-beta.1).

OpenClaw version

v2026.3.23 (built from tag). Also verified on main at 2026.3.24-beta.1 — same behavior, same unmodified shouldSkipBackendSelfPairing function.

Operating system

Ubuntu (headless, on GMKtec K8 Plus mini PC). Docker Engine + Docker Compose v2. Host networking mode for gateway container.

Install method

Docker — built from source via git checkout refs/tags/v2026.3.23 && docker build. Deployed with Docker Compose using the repo's docker-compose.yml. CLI container uses network_mode: "service:openclaw-gateway".

Model

Not model-specific — affects all CLI commands regardless of model. Primary model in use: moonshot/kimi-k2.5.

Provider / routing chain

Not provider-specific. The failure occurs at the WebSocket device pairing layer before any model/provider interaction. Gateway listens on ws://127.0.0.1:18789, CLI connects via shared network namespace over loopback. Auth mode: token. No reverse proxy, no Tailscale, no TLS.

Additional provider/model setup details

Multi-agent deployment with three Telegram bots and one Discord bot. Mix of Anthropic Claude, Moonshot Kimi, and local Ollama (Qwen 2.5 Coder 14B) models. 14 cron jobs configured. Gateway and CLI containers share the same config directory via bind mount (${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw), same identity/device.json, same workspace volume. controlUi.dangerouslyDisableDeviceAuth: true and controlUi.allowInsecureAuth: true are set — these allow Web UI access but have no effect on CLI or internal WebSocket connections.

Logs, screenshots, and evidence

shell CLI attempt:

$ docker compose run --rm --entrypoint /usr/local/bin/openclaw openclaw-cli cron list
gateway connect failed: GatewayClientRequestError: pairing required
Error: gateway closed (1008): pairing required
Gateway target: ws://127.0.0.1:18789
Source: local loopback
Config: /home/node/.openclaw/openclaw.json
Bind: loopback

Gateway log (corresponding): [ws] closed before connect conn=ab484908-ae75-4144-be1c-8199ba6b32c9 remote=127.0.0.1 fwd=n/a origin=n/a host=127.0.0.1:18789 ua=n/a code=1008 reason=pairing required

Internal cron/tools failures:

[tools] cron failed: gateway closed (1008): pairing required
[tools] sessions_list failed: gateway closed (1008): pairing required
[tools] gateway failed: gateway closed (1008): pairing required

devices/pending.json remains ``[] after every attempt — no pairing request is ever created. Both containers read the same identity:

$ docker exec openclaw-openclaw-cli-1 cat /home/node/.openclaw/identity/device.json | grep deviceId
  "deviceId": "9856fe882d40ddd78449edbdf7c64411122464ed6766364d02a3b8abdbd6b6e1"
$ docker exec openclaw-openclaw-gateway-1 cat /home/node/.openclaw/identity/device.json | grep deviceId
  "deviceId": "9856fe882d40ddd78449edbdf7c64411122464ed6766364d02a3b8abdbd6b6e1"

Code analysis of shouldSkipBackendSelfPairing (identical on v2026.3.23 and main 2026.3.24-beta.1):

function shouldSkipBackendSelfPairing(params) {
    if (!(params.connectParams.client.id === GATEWAY_CLIENT_IDS.GATEWAY_CLIENT 
        && params.connectParams.client.mode === GATEWAY_CLIENT_MODES.BACKEND)) return false;
    // ...
}

CLI connects with mode: GATEWAY_CLIENT_MODES.CLI — excluded by the first check. PR #30801's isBackendSelfConnection identifier is absent from both builds.

Impact and severity

  • Affected: All Docker-based OpenClaw deployments using CLI commands or relying on internal callGateway() operations (cron tools, sessions_spawn, sessions_list). At least 12 prior issues report the same root cause (#2284, #4531, #4941, #6085, #6959, #9028, #12210, #16204, #19352, #20707, #23471, #30740).
  • Severity: Blocks workflow. CLI is completely unusable. Internal operations intermittently fail.
  • Frequency: 100% for CLI commands. Intermittent for internal callGateway() (cron, sessions_spawn).
  • Consequence: No CLI access for config management, device administration, cron inspection, or diagnostics. Cron jobs that use sessions_spawn or sessions_list fail silently. Users are forced to rely solely on Web UI or abandon Docker for native installs.

Additional information

This is not a regression — it has never worked in Docker. The pairing system was designed for remote mobile/external clients and has no accommodation for co-located containers.

PR #30801 (merged to main March 2, 2026, closing #30740) added isBackendSelfConnection detection to broaden the bypass. However, this identifier is absent from the built dist output on both v2026.3.23 and main at 2026.3.24-beta.1 — suggesting the fix was either reverted, not included in the build pipeline, or overwritten by a subsequent refactor.

Even if #30801 were present, it would only cover GATEWAY_CLIENT + BACKEND mode (gateway-internal processes). CLI connections use GATEWAY_CLIENT_MODES.CLI and would still be blocked. The fix needs to be extended to cover CLI mode connections from localhost with valid shared auth — the same trust conditions, just a broader client mode match.

shouldAllowSilentLocalPairing exists as a secondary bypass that should auto-approve local connections, but it is never reached because the gateway rejects connections from its own deviceId before creating a pairing request (self-pairing prevention). Giving the CLI a separate identity avoids self-pairing prevention but still fails — pending.json remains empty, suggesting the rejection occurs even earlier in the handshake.

Related issues: #2284, #4531, #4941, #6085, #6959, #9028, #12210, #16204, #19352, #20707, #23471, #30740, #42931, #45232.

extent analysis

Fix Plan

To resolve the issue, we need to modify the shouldSkipBackendSelfPairing function to include GATEWAY_CLIENT_MODES.CLI in the check. We also need to ensure that the isBackendSelfConnection identifier is included in the build output.

Code Changes

function shouldSkipBackendSelfPairing(params) {
    if (!(params.connectParams.client.id === GATEWAY_CLIENT_IDS.GATEWAY_CLIENT 
        && (params.connectParams.client.mode === GATEWAY_CLIENT_MODES.BACKEND 
            || params.connectParams.client.mode === GATEWAY_CLIENT_MODES.CLI))) return false;
    // ...
}

Additionally, we need to verify that the isBackendSelfConnection identifier is included in the build output. If it's not, we need to update the build pipeline to include it.

Configuration Changes

No configuration changes are required for this fix.

Infra/Dependency Fixes

No infra/dependency fixes are required for this fix.

Verification

To verify that the fix worked, run the following command:

docker compose run --rm --entrypoint /usr/local/bin/openclaw openclaw-cli cron list

If the command succeeds without any errors, the fix has worked. You can also check the gateway logs to ensure that the pairing required error is no longer present.

Extra Tips

To prevent similar issues in the future, it's recommended to:

  • Thoroughly test CLI commands and internal callGateway() operations in Docker deployments.
  • Verify that build outputs include all required identifiers and fixes.
  • Regularly review and update the shouldSkipBackendSelfPairing function to ensure it covers all necessary client modes.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

CLI commands should succeed when the CLI container shares the gateway's network namespace (network_mode: "service:openclaw-gateway"), reads the same identity/device.json, and has access to the same gateway auth token. The Docker install docs show CLI commands working via docker compose run --rm openclaw-cli <command>.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING