← Back to PRs

#23503: fix: preserve pairing state on device token mismatch + migrate legacy paired.json

by dorukardahan open 2026-02-22 11:05 View on GitHub →
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