Auto-recover subscription sync after a PDS migration#291
Merged
disnet merged 1 commit intoJun 13, 2026
Conversation
Subscriptions silently stopped syncing after a user migrated their PDS: the host is resolved once at login into sessions.pds_url + did_pds_cache and never re-resolved, so authenticated PDS calls kept hitting the dead/old host and failing. Only login/callback evicted+re-resolved; the sync path didn't. Add one-shot stale-endpoint recovery in PDSClient. When a request fails in a way that looks like the host moved (network throw / 401 / 403 / invalid_token, but NOT RecordNotFound / 429 / 5xx / use_dpop_nonce), and a recovery context is supplied, the client evicts the DID cache, re-resolves the host from the DID doc, and — only if the host actually changed — persists it to the session (via new updateSessionPdsUrl, since storeSession's upsert doesn't touch pds_url), resets the per-host DPoP nonce, and retries once. If the new host still rejects the tokens, it surfaces needsReauth (no loop) so Settings can prompt the user to reconnect in Atmosphere voice. Wired through syncSubscriptions / /api/sync/full via the request's session id, so the same sync the toggle triggers now self-heals with no manual toggle. Regression coverage: PDSClient recovery unit tests (re-resolve+persist+retry+ success; needsReauth + no-loop; 5xx/not-found don't trigger; unchanged host doesn't retry; no-context is a no-op) and sync-path tests on the migration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A user migrated their PDS (changed PDS host) and their RSS subscriptions silently stopped syncing. Toggling Atmospheric sync off and back on appeared to fix it.
Root cause: the PDS host is resolved once at login into
sessions.pds_urlanddid_pds_cache(24h TTL) and never re-resolved.PDSClient.requestbinds both the request URL and the DPoPhtuclaim tosession.pdsUrl. After a migration the DID document's service endpoint changes but the session keeps the old host, so every authenticated PDS request fails (network / 401 / 403) and sync stops. Only the login/callback flow ever evicted + re-resolved the cache — the sync path andrefreshSessionreused the stale host.On Phase 1 (confirm the toggle's recovery):
/api/sync/fullreadsessions.pds_urlverbatim and never re-resolved, so the toggle alone could not repair the host. The fix therefore makes recovery happen in the request path itself, covering both possible migration shapes — host-only (existing tokens still valid → full auto-recovery) and a full account migration (tokens rejected by the new auth server → an explicit re-auth signal).Fix
One-shot stale-endpoint recovery centralized in
PDSClient:PDSRecoveryContext({ env, sessionId }) enables self-healing. When a request fails in a way that looks like a moved host — network throw /401/403/invalid_token, excludingRecordNotFound,429,5xx, anduse_dpop_nonce— the client evicts the DID cache, re-resolves the host from the DID doc, and only if the host actually changed persists it to the session, resets the per-host DPoP nonce, and retries once.updateSessionPdsUrlinoauth.tspersists the migrated host (storeSession'sON CONFLICTdeliberately doesn't touchpds_url).needsReauth, which flows throughsyncSubscriptions→/api/sync/full→ the frontend, where Settings shows a calm Atmosphere-voice prompt to sign in again.Recovery is wired through
syncSubscriptions//api/sync/fullusing the request's session id, so the same sync the Settings toggle fires now self-heals automatically — no manual toggle needed.Tests
pds-client.spec.ts— re-resolve + persist + retry + success;needsReauthwith no retry loop;5xxandRecordNotFounddon't trigger recovery; unchanged re-resolved host doesn't retry; no recovery context is a no-op.subscription-sync.spec.ts— sync-path migration: a seeded stale session recovers and pulls records without a toggle; new host still rejecting →needsReauth.All affected backend suites pass;
npm run checkis clean on backend and frontend.