openclaw - 💡(How to fix) Fix [Bug]: Remote CLI TUI stuck in device-pairing-required loop after device wipe [1 comments, 2 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#73636Fetched 2026-04-29 06:17:08
View on GitHub
Comments
1
Participants
2
Timeline
2
Reactions
0
Author
Timeline (top)
closed ×1commented ×1

Remote CLI openclaw tui (against a tailnet-fronted gateway) is locked into a device pairing required loop after a gateway-side device wipe: every connect opens a brand-new pending pair request that expires silently before any operator could approve it, even though the IP already has a paired entry. The asymmetry between the scopes the initial pair flow grants and the scopes the TUI immediately tries to use makes the pair effectively unusable.

Error Message

gateway connect failed: Error: gateway closed (1000): gateway connect failed: Error: gateway request timeout for connect gateway connect failed: GatewayClientRequestError: device pairing required (requestId: 10e7989f-c743-48d7-b57d-fc83101cd2c6)

Root Cause

openclaw devices list shows pending=0 outside narrow windows because the pending requests time out faster than an operator can poll. The single paired entry that does exist for the client's IP carries scopes=[operator.admin] only — narrower than the TUI's connect-time scope set, so the next connect treats it as if no pair exists.

Fix Action

Fix / Workaround

A small, non-protocol-changing CLI-level mitigation that would unblock operators today: extend openclaw devices approve with --scopes <a,b,c> and --role <name> flags so an operator can grant broader-than-requested scopes at approval time and break the cycle. Happy to follow up with that as a small PR if it sounds useful.

Workarounds tried

  • Setting gateway.trustedProxies = ["127.0.0.1","::1"]: fixed the upstream pairing-required, but exposed the application-level pair loop (this bug).
  • Stashing client-side device.json and re-pairing: succeeds once with a narrow pair; subsequent connects loop.
  • Removing the narrow paired entry on the server: each fresh connect re-bootstraps a new narrow pair via the same flow.
  • openclaw devices approve --latest from a polling loop: rubber-stamps whatever scope set the current pending request asks for. Initial requests ask for the minimum, so this only converges on a narrow pair.

Code Example

17:36:19  device pairing required  requestId: 2508bb69-d6ff-41ef-8a2a-45e2283b8bde
18:00:28  device pairing required  requestId: c3d0226a-5c76-4e9c-87b7-7e5a16a8bbf6
18:11:58  device pairing required  requestId: 10e7989f-c743-48d7-b57d-fc83101cd2c6

---

{"subsystem":"gateway/ws"} {'cause': 'pairing-required', 'handshake': 'failed', 'durationMs': 142, 'lastFrameType': 'req', 'lastFrameMethod': 'connect', 'host': '<host>.tail<hash>.ts.net', 'forwardedFor': '<tailnet-IP>', ...}
{"subsystem":"gateway/ws"} Proxy headers detected from untrusted address. Connection will not be treated as local. Configure gateway.trustedProxies to restore local client detection behind your proxy.

---

gateway connect failed: Error: gateway closed (1000):
gateway connect failed: Error: gateway request timeout for connect
gateway connect failed: GatewayClientRequestError: device pairing required (requestId: 10e7989f-c743-48d7-b57d-fc83101cd2c6)

---

Paired (1)
  <id>...   operator   operator.admin   <tailnet-IP>     <-- narrow, won't satisfy TUI
Pending (0)                                              <-- requests expire faster than anyone can list
RAW_BUFFERClick to expand / collapse

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

Remote CLI openclaw tui (against a tailnet-fronted gateway) is locked into a device pairing required loop after a gateway-side device wipe: every connect opens a brand-new pending pair request that expires silently before any operator could approve it, even though the IP already has a paired entry. The asymmetry between the scopes the initial pair flow grants and the scopes the TUI immediately tries to use makes the pair effectively unusable.

Steps to reproduce

  1. Configure a remote tailnet host with gateway.mode: remote, gateway.remote.url: wss://<host>.tail<hash>.ts.net, gateway.remote.token: <REDACTED>. Confirm tailscale ping <host> is direct and Invoke-WebRequest https://<host>.tail<hash>.ts.net/ returns 200.
  2. On the gateway host, set gateway.trustedProxies = ["127.0.0.1","::1"] so tailscale serve's X-Forwarded-* headers are trusted (otherwise step 5 would fail with cause=pairing-required upstream of the bug).
  3. On the gateway host, run openclaw devices clear --yes --pending to wipe paired/pending devices.
  4. On the remote, move the stale device identity aside: Move-Item "$env:USERPROFILE\.openclaw\identity\device.json" "$env:USERPROFILE\.openclaw\identity\device.json.stash-$(Get-Date -Format yyyyMMdd-HHmmss)".
  5. Run openclaw tui on the remote. A pending pair request appears server-side.
  6. Approve via openclaw devices approve --latest. Pair succeeds with scopes = [operator.admin] only.
  7. Run openclaw tui again on the remote.

Expected behavior

The existing pair entry from step 6 is reused and the TUI connects. If the TUI's connect-time scope set legitimately exceeds the paired set, the server emits a single scope.upgrade.pending request (the protocol clearly supports this — the message scope upgrade pending approval (requestId: ...) is emitted in other paths) and the existing device id is preserved.

Actual behavior

Each subsequent connect emits a fresh device pairing required (requestId: <new uuid>) server-side, opens a new pending pair request, and that request expires (silently) before an operator can act on it. The cycle repeats indefinitely. The TUI shows alternating gateway closed (1000): (server-side challenge timeout) and device pairing required (requestId: ...) errors.

Sample server-side log entries (a single afternoon, single client):

17:36:19  device pairing required  requestId: 2508bb69-d6ff-41ef-8a2a-45e2283b8bde
18:00:28  device pairing required  requestId: c3d0226a-5c76-4e9c-87b7-7e5a16a8bbf6
18:11:58  device pairing required  requestId: 10e7989f-c743-48d7-b57d-fc83101cd2c6

openclaw devices list shows pending=0 outside narrow windows because the pending requests time out faster than an operator can poll. The single paired entry that does exist for the client's IP carries scopes=[operator.admin] only — narrower than the TUI's connect-time scope set, so the next connect treats it as if no pair exists.

OpenClaw version

2026.4.26 (gateway and client, both)

Operating system

Gateway: Ubuntu 24.04 (n2-standard-4 GCE VM). Clients: two Windows 11 tailnet hosts (PowerShell 7 in both cases). Both reproduce the loop independently — it is not a single-host artifact.

We have not tested a Linux or macOS tailnet client against the same gateway, so the report cannot rule out an OS-specific factor on the Windows side. That said, the failure is observed in the connect/pair RPC handler on the gateway (rolling log shows device pairing required from subsystem=gateway/ws) — i.e., the rejection comes from code shared across all client OSes, with no Windows-specific signal in any of the redacted log lines. We expect a Linux client to reproduce the same loop, but that is unverified.

Install method

Gateway: pnpm global (~/.npm-global/bin/openclaw), supervised by a systemd user unit. Client: npm global on Windows.

Model

anthropic/claude-opus-4-7 (gateway agent default)

Provider / routing chain

remote client (Win11 PowerShell) → tailscale serve (TLS termination at 127.0.0.1 on the gateway host) → openclaw-gateway on 127.0.0.1:18789 → anthropic provider

Additional provider/model setup details

This bug is in the connect/pair protocol, not in any model path; the model row is included only for template completeness. Relevant configuration:

  • gateway.bind: loopback
  • gateway.auth.mode: token
  • gateway.tailscale.mode: serve
  • gateway.controlUi.allowedOrigins: ["https://<host>.tail<hash>.ts.net"]
  • gateway.trustedProxies: ["127.0.0.1","::1"]

Logs, screenshots, and evidence

Server-side rolling log (/tmp/openclaw/openclaw-YYYY-MM-DD.log), redacted excerpt:

{"subsystem":"gateway/ws"} {'cause': 'pairing-required', 'handshake': 'failed', 'durationMs': 142, 'lastFrameType': 'req', 'lastFrameMethod': 'connect', 'host': '<host>.tail<hash>.ts.net', 'forwardedFor': '<tailnet-IP>', ...}
{"subsystem":"gateway/ws"} Proxy headers detected from untrusted address. Connection will not be treated as local. Configure gateway.trustedProxies to restore local client detection behind your proxy.

After applying gateway.trustedProxies, the upstream cause=pairing-required is replaced by the application-level device pairing required rejection from the connect RPC handler — the loop documented in Actual behavior.

Client-side rolling log (%TEMP%\openclaw\openclaw-YYYY-MM-DD.log), redacted excerpt:

gateway connect failed: Error: gateway closed (1000):
gateway connect failed: Error: gateway request timeout for connect
gateway connect failed: GatewayClientRequestError: device pairing required (requestId: 10e7989f-c743-48d7-b57d-fc83101cd2c6)

Server-side device list during the loop:

Paired (1)
  <id>...   operator   operator.admin   <tailnet-IP>     <-- narrow, won't satisfy TUI
Pending (0)                                              <-- requests expire faster than anyone can list

Impact and severity

  • Affected: any operator running openclaw tui from a tailnet host against a tailscale serve-fronted gateway after openclaw devices clear --yes. Reproduced from two distinct Windows tailnet hosts in the same session.
  • Severity: blocks workflow. The remote CLI TUI is the documented path for tailnet operators; with this loop it is effectively unusable. Web UI from the same hosts works (different auth surface), and locally-run TUI on the gateway host works.
  • Frequency: 100% reproducible on the configuration described.
  • Consequence: operators must SSH to the gateway and run TUI locally, or fall back to the Web UI. The bug also tarpits the operator UI: pending requests come and go faster than openclaw devices list can poll, so the server-side state appears to be "nothing happening" while the loop is in fact ongoing.

Additional information

Three concrete defects compound in this loop. Splitting them in case they want to be triaged separately:

  • A. Initial-pair flow grants only operator.admin, narrower than the scope set the TUI uses on its very next connect.
  • B. When a connect requests broader scopes than the existing pair grants, the server opens a new pending pair request instead of a scope.upgrade request. The existing device id is discarded.
  • C. openclaw devices list shows pending requests only while they are alive (≈ tens of seconds). After expiry they leave no operator-visible trace, making the loop very hard to diagnose without rolling-log access.

A small, non-protocol-changing CLI-level mitigation that would unblock operators today: extend openclaw devices approve with --scopes <a,b,c> and --role <name> flags so an operator can grant broader-than-requested scopes at approval time and break the cycle. Happy to follow up with that as a small PR if it sounds useful.

Side note on diagnosis on Windows

A separate paper-cut from this report but worth flagging: on Windows, the TUI's alternate-buffer terminal rendering swallows stdout/stderr even with OPENCLAW_LOG_LEVEL=debug, so capturing client-side trace required falling back to the rolling log at %TEMP%\openclaw\openclaw-YYYY-MM-DD.log. Mentioning in case the diagnosability angle is in scope; we'd file separately if preferred.

Workarounds tried

  • Setting gateway.trustedProxies = ["127.0.0.1","::1"]: fixed the upstream pairing-required, but exposed the application-level pair loop (this bug).
  • Stashing client-side device.json and re-pairing: succeeds once with a narrow pair; subsequent connects loop.
  • Removing the narrow paired entry on the server: each fresh connect re-bootstraps a new narrow pair via the same flow.
  • openclaw devices approve --latest from a polling loop: rubber-stamps whatever scope set the current pending request asks for. Initial requests ask for the minimum, so this only converges on a narrow pair.

Configuration redaction note

Tailnet hostname, tailnet IP, and device tokens have been redacted as <host>, <tailnet-IP>, <REDACTED>. Device IDs and request IDs are partial-real for grounding; full hex is not included. No tokens or auth material is included anywhere in this report.

extent analysis

TL;DR

The most likely fix is to modify the openclaw devices approve command to allow granting broader scopes at approval time, breaking the cycle of new pending pair requests.

Guidance

  • Identify the root cause of the issue: the initial pair flow grants only operator.admin scope, which is narrower than the scope set used by the TUI on its next connect.
  • Modify the openclaw devices approve command to accept --scopes and --role flags, allowing operators to grant broader scopes at approval time.
  • Test the modified openclaw devices approve command to ensure it breaks the cycle of new pending pair requests.
  • Consider implementing a scope.upgrade request instead of opening a new pending pair request when a connect requests broader scopes than the existing pair grants.
  • Review the openclaw devices list command to ensure it displays pending requests in a way that allows operators to diagnose the issue.

Example

No code snippet is provided as the issue requires modifications to the openclaw devices approve command and potentially the openclaw devices list command, which are not explicitly defined in the issue.

Notes

The issue is specific to the openclaw tui command and the tailscale serve-fronted gateway configuration. The proposed fix is a CLI-level mitigation that would unblock operators, but a more comprehensive solution may require changes to the underlying protocol.

Recommendation

Apply the workaround by modifying the openclaw devices approve command to accept --scopes and --role flags, allowing operators to grant broader scopes at approval time. This will break the cycle of new pending pair requests and provide a temporary solution until a more comprehensive fix is implemented.

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

The existing pair entry from step 6 is reused and the TUI connects. If the TUI's connect-time scope set legitimately exceeds the paired set, the server emits a single scope.upgrade.pending request (the protocol clearly supports this — the message scope upgrade pending approval (requestId: ...) is emitted in other paths) and the existing device id is preserved.

Still need to ship something?

×6

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

Back to top recommendations

TRENDING