#17379: fix: restore device token priority in device-auth mode
gateway
stale
size: XS
Fixes #17270, #16820, #16862, #17223, #17233
---
## Problem
Commit [d8a2c80cd](https://github.com/openclaw/openclaw/commit/d8a2c80cd) ("fix(gateway): prefer explicit token over stored auth") introduced a **token priority regression** that breaks device authentication for all non-localhost paired devices.
**Symptom:**
After upgrading from v2026.2.13 to v2026.2.14, all previously paired devices (Node, CLI, browser) fail to connect with:
```
unauthorized: device token mismatch (rotate/reissue device token)
```
Downgrading to v2026.2.13 **immediately fixes** the issue without any config changes.
**Affected platforms:** Ubuntu, macOS, PopOS, Linux arm64 (#16820, #16862, #17223, #17233)
---
## Root Cause
### The Regression (d8a2c80cd)
The commit changed `src/gateway/client.ts` line 193:
```diff
- // v2026.2.13 — device token takes priority
- const authToken = storedToken ?? this.opts.token ?? undefined;
+ // v2026.2.14 — config/env token takes priority
+ const authToken = this.opts.token ?? storedToken ?? undefined;
```
**Intent:** Allow CLI `--token` to override stored device tokens.
**Problem:** `this.opts.token` contains **both**:
1. **Explicit CLI overrides** (`--token <value>`) ← should take priority ✅
2. **Config file defaults** (`gateway.token`) ← should NOT override device tokens ❌
The commit treated both as "explicit" and broke device authentication.
---
### Why It Breaks Device Auth
**Device pairing flow:**
1. Device pairs with gateway → receives **device-specific token**
2. Token stored locally in `~/.openclaw/auth/devices/<deviceId>.json`
3. Device connects → should send **stored device token**
**In v2026.2.14:**
- Device has `storedToken` (device-specific) ✅
- Config has `gateway.token` (shared gateway token) ⚠️
- **Priority:** `opts.token ?? storedToken` → sends **shared token** ❌
- Gateway rejects: **"device token mismatch"** ❌
**In v2026.2.13:**
- **Priority:** `storedToken ?? opts.token` → sends **device token** ✅
- Gateway accepts ✅
---
## Solution
**Restore device token priority when `deviceIdentity` is present:**
```typescript
const authToken =
this.opts.deviceIdentity && storedToken
? storedToken // ✅ Device-auth mode: use device-specific token
: this.opts.token ?? storedToken ?? undefined; // Shared-auth or fallback
```
**Logic:**
- **Device-auth mode** (`deviceIdentity` + `storedToken` exist):
- Use stored device token (ignore config `gateway.token`)
- Ensures paired devices continue working after config changes/upgrades
- **Shared-auth mode** (no `deviceIdentity` or no `storedToken`):
- Use `opts.token` (CLI `--token` or config `gateway.token`)
- Fallback to `storedToken` if available
**Why this is correct:**
- When a device is paired, `deviceIdentity` is set (contains `deviceId`, `deviceName`)
- Paired devices **must** use their device-specific token (not shared token)
- Config `gateway.token` is for **unpaired clients** or **shared authentication**
---
## Impact
### ✅ Fixed
- Paired devices (Node, CLI, browser) authenticate successfully after v2026.2.14 upgrade
- Device tokens are respected in device-auth mode
- Config `gateway.token` still works for unpaired clients
- **No manual token rotation or re-pairing required**
### ✅ Preserves Original Intent
- CLI `--token` **still overrides** device token when device is **not yet paired**
- After pairing, device token takes priority (correct security model)
### 📝 Known Limitation
If user explicitly wants to override a paired device's token via CLI `--token`:
1. Clear device token: `rm ~/.openclaw/auth/devices/<deviceId>.json`
2. Then use `openclaw connect --token <new-token>`
**This is acceptable because:**
- Overriding paired device tokens is rare (users normally re-pair instead)
- Device auth is **intentionally sticky** (security best practice)
- Users can always re-pair to change tokens
---
## Testing
**Manual verification:**
1. Pair a device on v2026.2.13 (or clean install + this PR)
2. Verify stored token: `cat ~/.openclaw/auth/devices/<deviceId>.json`
3. Add `gateway.token` to config (simulating shared token)
4. Reconnect → uses device token, ignores config token ✅
**Regression check:**
1. Test on v2026.2.14 (before this PR) → fails ❌
2. Apply this PR → succeeds ✅
3. Upgrade path: v2026.2.13 → v2026.2.14 + PR → no breakage ✅
---
## Related Issues
This PR fixes **5 issues** sharing the same root cause (d8a2c80cd priority flip):
- #17270 - Device token auth regression in v2026.2.14
- #16820 - PopOS: device token mismatch after update
- #16862 - Unauthorized: device token mismatch
- #17223 - systemd service: device token mismatch
- #17233 - Safari webchat: requires macOS logout after v2026.2.14
All reports describe the same pattern:
1. Works on v2026.2.13 ✅
2. Breaks on v2026.2.14 ❌
3. Downgrade to v2026.2.13 → works again ✅
**Root cause:** Token priority flip in d8a2c80cd.
---
**Note:** This is a clean re-submission of the previously closed PR #17296 (which had unrelated commits). This branch contains only the single targeted fix.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Fixes a token priority regression introduced in v2026.2.14 (commit d8a2c80cd) that broke device authentication for all non-localhost paired devices. The original commit flipped `storedToken ?? opts.token` to `opts.token ?? storedToken`, causing config-file `gateway.token` values to override device-specific tokens — resulting in "device token mismatch" errors across platforms.
This PR restores device token priority by preferring `storedToken` when both `deviceIdentity` and a stored token are present.
- **Fixes 5 related issues** (#17270, #16820, #16862, #17223, #17233) sharing the same root cause
- The effective behavior matches v2026.2.13: stored device tokens take priority, with `opts.token` as fallback when no stored token exists
- **No unit tests** were added for the token priority logic in `sendConnect()`. Given this is a regression fix for a subtle priority ordering bug, a unit test verifying the correct token selection under both conditions (stored token present vs. absent) would help prevent future regressions.
<h3>Confidence Score: 4/5</h3>
- This PR is safe to merge — it correctly restores the pre-regression device token priority behavior with a minimal, targeted change.
- The fix is a single-line logic change that effectively restores the pre-regression behavior. The deviceIdentity guard in the ternary is always truthy (as noted in prior review), making the condition equivalent to checking storedToken first and falling back to opts.token — which matches the original logic. The change is correct and addresses a real regression. Score is 4 rather than 5 due to the absence of unit tests for this specific token priority logic, which is the exact area where the original regression occurred.
- No files require special attention beyond the already-discussed `deviceIdentity` truthy guard in `src/gateway/client.ts`.
<sub>Last reviewed commit: 51d7026</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#17279: fix: restore device token priority over config token
by MisterGuy420 · 2026-02-15
90.3%
#17336: fix(gateway): restore device token priority over passive config token
by milosm · 2026-02-15
85.0%
#23503: fix: preserve pairing state on device token mismatch + migrate lega...
by dorukardahan · 2026-02-22
84.7%
#16310: fix(ws-connection): skip device pairing when client authenticates w...
by nawinsharma · 2026-02-14
83.9%
#15722: fix: prefer explicit token over stored device token for remote gate...
by 0xPotatoofdoom · 2026-02-13
83.4%
#17572: fix: make dangerouslyDisableDeviceAuth bypass device identity checks
by gitwithuli · 2026-02-15
79.5%
#21651: fix(gateway): token fallback + operator.admin scope superset in pai...
by lan17 · 2026-02-20
79.5%
#20422: Fix/tailscale device pairing
by slagyr · 2026-02-18
79.3%
#19885: test(gateway,browser): isolate tests from ambient OPENCLAW_GATEWAY_...
by NewdlDewdl · 2026-02-18
79.1%
#17705: fix(gateway): allow trusted-proxy auth to bypass device-pairing gates
by dashed · 2026-02-16
78.9%