#23503: fix: preserve pairing state on device token mismatch + migrate legacy paired.json
gateway
size: S
## Summary
This PR fixes two related device pairing recovery issues that can break local gateway/CLI auth recovery after `device_token_mismatch`.
### What changed
1. Preserve paired device entries on `device token mismatch`
- `GatewayClient` now clears only the local cached device-auth token.
- It no longer clears the gateway pairing entry on mismatch.
- This lets the gateway issue a fresh device token on the next connect instead of causing a self-unpair loop.
2. Normalize legacy `paired.json` array state on load
- `src/infra/device-pairing.ts` now accepts legacy `paired.json` array format (`[]`) and converts it into the current object-map shape keyed by `deviceId`.
- This prevents `devices approve` from appearing to succeed while failing to persist the pair in the expected format.
## Why
In practice, these two behaviors combine into a difficult recovery loop:
- a mismatch clears local token state,
- pairing state can also be removed,
- and legacy `paired.json` arrays can prevent approvals from persisting correctly.
Result: repeated `device_token_mismatch` / `pairing required` loops and broken CLI/gateway connectivity.
## Tests
Targeted tests run locally:
- `pnpm exec vitest run --config vitest.gateway.config.ts src/gateway/client.test.ts`
- `pnpm exec vitest run --config vitest.unit.config.ts src/infra/device-pairing.test.ts`
Both passed.
Added/updated regressions:
- `GatewayClient` mismatch close handling preserves paired device entry
- `device-pairing` migrates legacy `paired.json` array format and persists by `deviceId`
## Real-world validation
Validated on a real Linux VPS deployment affected by this issue (local loopback gateway setup):
- pairing state persists after approval
- device-auth token is reissued and stored
- restart no longer clears the paired device entry in the mismatch path
## Issue
Fixes #23498
## AI-assisted
- [x] AI-assisted PR (Codex)
- [x] Lightly tested (targeted unit tests + real deployment validation)
- [x] I understand what the code does
- [ ] Included prompts/session logs
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR fixes two device pairing recovery issues:
1. **Preserves pairing state on token mismatch**: Changed `GatewayClient` to clear only the local device-auth token on `device_token_mismatch`, while preserving the gateway pairing entry. Previously, both the local token and the pairing entry were cleared, causing a self-unpair loop.
2. **Migrates legacy `paired.json` format**: Added normalization logic in `device-pairing.ts` to convert legacy array-based `paired.json` (`[]`) into the current object-map format keyed by `deviceId`. This prevents approvals from appearing to succeed while failing to persist correctly.
**Changes**:
- `src/gateway/client.ts`: Removed `clearDevicePairing()` call from mismatch handler (lines 178-191), preserving paired device entry
- `src/infra/device-pairing.ts`: Added `normalizePairedState()` helper to migrate legacy array format to object-map format (lines 90-108)
- Tests updated to verify new behavior: pairing preserved on mismatch, legacy format migration works correctly
The fix addresses a difficult recovery loop where token mismatches combined with legacy array state prevented successful re-pairing.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- The changes are well-tested with targeted regression tests, fix a specific recovery loop issue, and follow defensive coding practices. The migration logic handles legacy state gracefully with proper validation. The fix has been validated on a real deployment.
- No files require special attention
<sub>Last reviewed commit: 41f8390</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#21148: fix(gateway): add request-aware pairing recovery hints and docs
by cluster2600 · 2026-02-19
84.8%
#17379: fix: restore device token priority in device-auth mode
by Limitless2023 · 2026-02-15
84.7%
#17279: fix: restore device token priority over config token
by MisterGuy420 · 2026-02-15
84.1%
#6846: fix: bridge node.pair.* tools to device pairing store
by cortexuvula · 2026-02-02
83.0%
#16310: fix(ws-connection): skip device pairing when client authenticates w...
by nawinsharma · 2026-02-14
83.0%
#22712: fix(gateway): auto-approve all device pairing for localhost connect...
by NewdlDewdl · 2026-02-21
82.6%
#19088: fix(gateway): allow startup with unset mode and fix pairing for local…
by mdanassaif · 2026-02-17
82.6%
#22280: fix(gateway): silently auto-approve local paired-device scope upgrades
by abhishekp76 · 2026-02-21
81.4%
#17705: fix(gateway): allow trusted-proxy auth to bypass device-pairing gates
by dashed · 2026-02-16
81.4%
#22262: fix(gateway): add pairing-repair command hint on 1008 connect failures
by karimnaguib · 2026-02-20
81.0%