Skip to content

iOS LAN pairing drops bootstrap handoff token before chat reconnect #98064

Description

@ooiuuii

Summary

The iOS app can appear paired to a LAN Gateway after scanning /pair qr, but the next node/chat connection is rejected with token_missing. The setup bootstrap is accepted once, then the app reconnects without any gateway auth token.

Real environment observed

  • Gateway / Control UI: OpenClaw 2026.6.11-beta.2 on Windows, LAN bind 0.0.0.0:18889, token auth enabled
  • iOS app: OpenClaw/1 / openclaw-ios node client v2026.6.10
  • QR payload path: /pair qr generated a LAN WebSocket URL like ws://192.168.50.164:18889 with a bootstrap token
  • User-visible state: Control tab showed the Main gateway online, but Chat showed Disconnected / not connected

User-visible symptom

After scanning the pairing QR, the app looks partly connected because the Control/Gateway card is online, but Chat cannot connect and the Gateway still reports that a token is required.

Source-level repro on current main

  1. Use a setup code with a private LAN plaintext WebSocket URL such as ws://192.168.50.164:18889 and a bootstrapToken.
  2. The deep link parser accepts private LAN ws:// setup codes.
  3. The first node connection can authenticate via the bootstrap token.
  4. GatewayChannel.shouldPersistBootstrapHandoffTokens() only persists bootstrap-issued device tokens for wss:// or loopback URLs.
  5. Because the LAN URL is ws://192.168.x.x, the iOS client does not persist the issued node/operator device tokens.
  6. The app clears the spent bootstrap token after the first successful bootstrap connect.
  7. The next node/chat WebSocket reconnect sends no auth and is rejected as token_missing.

Log evidence

Redacted Gateway log sequence from the real repro:

device pairing auto-approved ... role=node
unauthorized ... client=iPhone node v2026.6.10 role=node scopes=0 auth=none ... reason=token_missing
closed before connect ... reason=unauthorized: gateway token missing (provide gateway auth token)

Expected behavior

If iOS accepts private LAN ws:// setup codes, the successful bootstrap handoff should persist the bounded device tokens issued by the Gateway so subsequent node/chat reconnects can authenticate without reusing the spent bootstrap token.

Actual behavior

The bootstrap handoff succeeds once, but the device token is not persisted for private LAN ws:// URLs. The next reconnect has auth=none and the app remains disconnected from Chat.

Scope / non-goals

  • Do not allow public/non-local plaintext WebSocket setup codes.
  • Do not change token validation or accept expired bootstrap tokens.
  • Keep existing behavior for wss:// and loopback.
  • This should only align private LAN ws:// token persistence with the setup-code hosts the iOS app already accepts.

Proposed fix

Allow GatewayChannel.shouldPersistBootstrapHandoffTokens() to persist bootstrap handoff tokens when the active URL is ws:// and the host is local-network scoped according to LoopbackHost.isLocalNetworkHost. Add a Swift regression test for a private LAN URL and keep the existing public ws://example.invalid non-persistence test.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:needs-live-reproClawSweeper needs live local, crabbox, or manual validation to confirm this issue.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.impact:auth-providerAuth, provider routing, model choice, or SecretRef resolution may break.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🐚 platinum hermitGood issue quality with a plausible reproduction path needing some confirmation.maturity:stableIssue affects a taxonomy feature currently scored M4/M5.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions