* fix(pairing): honor gateway.publicOrigin for device join codes A loopback Gateway behind public HTTPS ingress sets gateway.publicOrigin, and cloud node enrollment already used it, but device join codes, the device-pair plugin, and Doctor's node-hosting check each resolved the pairing endpoint on their own and ignored it, failing with advice that omitted publicOrigin. Make src/pairing/setup-code.ts the single owner (device-pair publicUrl override, then gateway.publicOrigin, then existing discovery), expose it to the device-pair plugin through plugin-sdk/device-bootstrap, remove the plugin's duplicate resolver and enrollment's private fallback, and share one loopback error that names gateway.publicOrigin. * fix(pairing): preserve explicit remote endpoint selection Honor preferRemoteUrl before gateway.publicOrigin so qr --remote keeps its selected endpoint aligned with remote credentials. Retain publicOrigin as this Gateway's ingress ahead of automatic Tailscale, remote, and bind discovery, with the pairing-specific override taking precedence. Cover configurations with both URLs at the resolver and QR CLI boundaries, and correct the CLI, cloud-worker, and SDK precedence documentation. Validation: 221 focused tests passed; scoped core, extension, script, and test typechecks passed; independent review found no actionable P0-P2 issues. * fix(pairing): preserve device routes with explicit cloud ingress preference Keep existing device join-code, QR, and /pair discovery order. Use publicOrigin only at the loopback fallback, while cloud enrollment asks the same resolver to prefer public ingress at both preparation and issuance. Use the canonical lazy runtime binder for the SDK export so the collision guard recognizes one implementation without loading setup code at startup. Document the two call-site intents and cover Tailscale, LAN, loopback, and cloud enrollment behavior. Validation: export-name collision and SDK surface guards passed; 222 focused tests and scoped typechecks passed; independent review was clean. * chore(device-pair): drop the max-lines suppression the smaller plugin no longer needs * fix(device-pair): preserve origin-only setup URLs Retain the plugin command URL mapping through the shared pairing resolver while preserving context paths for join codes and cloud enrollment. Clarify public-origin fallback and remote QR prerequisites.
14 KiB
| summary | read_when | title | ||
|---|---|---|---|---|
| CLI reference for `openclaw devices` (device pairing + token rotation/revocation) |
|
Devices |
openclaw devices
Manage device pairing requests and device-scoped tokens.
Common options
--url <url>: Gateway WebSocket URL (defaults togateway.remote.urlwhen configured)--token <token>: Gateway token (if required)--password <password>: Gateway password (password auth)--timeout <ms>: RPC timeout--json: JSON output (recommended for scripting)
Commands
openclaw devices list
List pending pairing requests and paired devices.
openclaw devices list
openclaw devices list --json
For a pending request on an already-paired device, the output shows requested access next to the device's current approved access, so scope/role upgrades are visible instead of looking like a lost pairing.
Paired device display names use this precedence: operator label (operatorLabel from devices rename), then client displayName, then clientId, then deviceId. Node approval notices printed by devices commands use the operator label when one is set.
openclaw devices approve [requestId] [--latest]
Approve a pending pairing request by exact requestId. Omitting requestId, or passing --latest, only previews the newest pending request and exits (code 1); rerun with the exact request ID to approve.
The printed approval command keeps your active profile or container, explicit Gateway URL, nondefault timeout, and JSON output mode. Token and password option values are omitted; supply the same credentials again when the preview asks you to reuse those options.
openclaw devices approve
openclaw devices approve <requestId>
openclaw devices approve --latest
If a device retries pairing with changed auth details (role, scopes, or public key), OpenClaw supersedes the previous pending entry with a new `requestId`. Run `openclaw devices list` right before approval to get the current id.
Approval behavior:
- If the device is already paired and requests broader scopes or role, OpenClaw keeps the existing approval and creates a new pending upgrade request. Compare
RequestedvsApprovedinopenclaw devices list, or preview with--latest, before approving. - Approving a
noderole or other non-operator role requiresoperator.admin.operator.pairingis enough for operator-device approvals, but only when the requested operator scopes stay within the caller's own scopes. See Operator scopes. - If
gateway.nodes.pairing.autoApproveCidrsis configured, first-timerole: noderequests from matching client IPs can be auto-approved before they appear in this list. Disabled by default; never applies to operator/browser clients or upgrade requests. gateway.nodes.pairing.sshVerify(on by default) auto-approves first-timerole: noderequests when the gateway verifies the device key over SSH to the node host. Requests may therefore resolve to approved shortly after appearing. SetsshVerify: falseto disable SSH verification; this is independent ofautoApproveCidrs, so unset that too for manual-only pairing.
openclaw devices reject <requestId>
Reject a pending device pairing request.
openclaw devices reject <requestId>
openclaw devices join-code
Mint a single-use node onboarding URL with administrator access to the
Gateway. Paste the printed npx openclaw connect <url> command on the machine
to enroll. This join URL is not a mobile app setup code; for Android/iOS use
openclaw qr instead.
openclaw devices join-code
openclaw devices join-code --json
Join-code creation and redemption are core Gateway operations; no pairing plugin needs to be enabled. The URL must be reachable from the joining machine. Remote join URLs require a TLS Gateway endpoint. Explicitly configured loopback endpoints can use HTTP, provided the joining machine can reach that loopback endpoint, for example through a local tunnel.
With only the default loopback bind and no advertised endpoint, URL discovery
refuses to mint a link. For a loopback Gateway behind public HTTPS ingress, set
gateway.publicOrigin to the proxy's bare HTTPS origin and include the proxy's
source address in gateway.trustedProxies.
Join codes, /pair, and QR setup preserve existing endpoint selection:
plugins.entries.device-pair.config.publicUrl, an explicitly preferred
gateway.remote.url, Tailscale Serve/Funnel, the non-preferred remote URL,
then bind-derived addresses. gateway.publicOrigin is used only as the final
fallback before the loopback-only error; it does not replace an existing route.
Callers targeting the local Gateway omit the remote URL. HTTP(S) URLs become
matching ws:/wss: pairing endpoints.
Join codes preserve the context path of a fully qualified publicUrl: for
https://pair.example/extra, the join URL begins with
https://pair.example/extra/j/. The device-pair plugin's /pair command instead
retains its historical origin-only WebSocket endpoint, wss://pair.example.
Cloud node enrollment uses the same resolver with an
explicit public-ingress preference: the pairing-specific override still wins,
then gateway.publicOrigin precedes discovery for freshly provisioned workers.
For other deployment prerequisites, see Gateway deployments that cannot host nodes. Plaintext LAN pairing can use a setup code directly instead of an HTTP join URL. See Connect a machine.
openclaw devices remove <deviceId>
Remove one paired device entry.
openclaw devices remove <deviceId>
openclaw devices remove <deviceId> --json
A caller authenticated with a paired device token can remove only its own device entry. Removing another device requires operator.admin.
openclaw devices rename --device <id> --name <label>
Assign an operator label to a paired device. Labels are owner-side state: they survive pairing repairs and role re-approvals, and they do not change the stable deviceId.
openclaw devices rename --device <deviceId> --name "Kitchen Mac"
openclaw devices rename --device <deviceId> --name "Kitchen Mac" --json
--nameis required, trimmed, non-empty, and capped at 64 characters.- Display surfaces (CLI list, Control UI inventory) prefer the operator label over the client-reported display name.
- A non-admin paired-device caller can rename only its own device. Renaming another device requires
operator.admin.
openclaw devices clear --yes [--pending]
Clear paired devices in bulk. Gated by --yes.
openclaw devices clear --yes
openclaw devices clear --yes --pending
openclaw devices clear --yes --pending --json
--pending also rejects all pending pairing requests.
openclaw devices rotate --device <id> --role <role> [--scope <scope...>]
Rotate a device token for a role, optionally updating its scopes.
openclaw devices rotate --device <deviceId> --role operator --scope operator.read --scope operator.write
- The target role must already exist in that device's approved pairing contract; rotation cannot mint a new unapproved role.
- Omitting
--scoperetains the target token's current scopes. Passing explicit--scopevalues replaces that scope set, within the device's approved baseline, for future cached-token reconnects. - Pass
--no-scopesto request an empty scope set. It cannot be combined with--scope. - A non-admin paired-device caller can rotate only its own device token, and the target scope set must stay within the caller's own operator scopes; rotation cannot mint or preserve a broader token than the caller already has.
Returns rotation metadata as JSON. If the caller rotates its own token while authenticated with that device token, the response includes the replacement token so the client can persist it before reconnecting. Shared-secret callers and callers rotating another device never receive the bearer token.
When Doctor reports a legacy node token carrying operator scopes, use its explicit recovery command:
openclaw devices rotate --device <deviceId> --role node --no-scopes
This recovery requires operator.admin and preserves the device's operator pairing and approved scopes. The Gateway removes only a local cached node token that matches the retired legacy token, in the same commit as rotation. For a node host using a separate state directory, provide valid shared Gateway authentication and restart the node to refresh its cache. A retired device token alone cannot authenticate the reconnect.
openclaw devices revoke --device <id> --role <role>
Revoke a device token for a role.
openclaw devices revoke --device <deviceId> --role node
A non-admin paired-device caller can revoke only its own device token. Revoking another device's token requires operator.admin. The target scope set must also fit within the caller's own operator scopes; pairing-only callers cannot revoke admin/write operator tokens.
Notes
- These commands require
operator.pairing(oroperator.admin) scope. Non-operator device roles always requireoperator.admin; see Operator scopes. - Token rotation and revocation stay inside the device's approved pairing role set and scope baseline. A stray cached token entry does not grant a token-management target.
- Rotation and revocation also invalidate the matching device and role's Dashboard read permissions and Cron caller authority retained by an admitted turn, including after a disconnect. Disconnecting alone does not revoke those permissions. Already committed Cron changes keep their outcome, and existing schedules are not canceled by revoking their creator's token.
- Removing a device or revoking its node token also clears node runtime state. A worker cleanup error does not keep affected connections authorized or open.
- For operator tokens, the CLI first reads the pairing list, then requests pairing plus the target token's scopes (or explicit rotate scopes). If the target is not visible, it requests admin access for cross-device management. A narrowed token does not inherit a broader device approval baseline; the caller must already be authorized for the requested scopes.
- For paired-device token sessions, cross-device management (
remove,rename,rotate,revoke) is self-only unless the caller hasoperator.admin. - Token rotation returns a new token (sensitive) — treat it like a secret.
- If pairing scope is unavailable on local loopback and no explicit
--urlis passed,list/approvecan fall back to local pairing state.
Token drift recovery checklist
Use this when Control UI or other clients keep failing with AUTH_TOKEN_MISMATCH, AUTH_DEVICE_TOKEN_MISMATCH, or AUTH_SCOPE_MISMATCH.
-
Confirm current gateway token source:
openclaw gateway auth-token --showRun the command in an interactive terminal on the Gateway host and treat its output as a secret.
-
List paired devices and identify the affected device id:
openclaw devices list -
Rotate the operator token for the affected device:
openclaw devices rotate --device <deviceId> --role operator -
If rotation is not enough, remove the stale pairing and approve again:
openclaw devices remove <deviceId> openclaw devices list openclaw devices approve <requestId> -
Retry the client connection with the current shared token/password.
Notes:
- Normal reconnect auth precedence: explicit shared token/password first, then explicit
deviceToken, then stored device token, then bootstrap token. - Trusted
AUTH_TOKEN_MISMATCHrecovery can temporarily send both the shared token and the stored device token together for one bounded retry. AUTH_SCOPE_MISMATCHmeans the device token was recognized but does not carry the requested scope set; fix the pairing/scope approval contract before changing shared gateway auth.
Related:
Paperclip / openclaw_gateway first-run approval
Paperclip agents connecting through the openclaw_gateway adapter go through the same first-run device pairing approval as any other new client. If Paperclip reports openclaw_gateway_pairing_required, approve the pending device and retry.
openclaw devices approve --latest
The preview prints the exact openclaw devices approve <requestId> command; verify the details, then rerun that command with the request ID to approve it. For a remote gateway or explicit credentials, pass the same options while previewing and approving:
openclaw devices approve --latest --url <gateway-ws-url> --token <gateway-token>
To avoid re-approving after every restart, configure a persistent adapterConfig.devicePrivateKeyPem in Paperclip instead of letting it generate a new ephemeral device identity each run:
{
"adapterConfig": {
"devicePrivateKeyPem": "<ed25519-private-key-pkcs8-pem>"
}
}
If approval keeps failing, run openclaw devices list first to confirm a pending request exists.
Related
- CLI reference
- Nodes
openclaw qr— generate the mobile-node bootstrap QR and setup code