[Bug]: Re-pairing a new server at the same address leaves a broken duplicate environment#15802

openedOct 4, 2026bySshivam

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/mobile

Steps to reproduce

  1. Start server A at http://127.0.0.1:13774 and pair the iOS app with it.
  2. Stop A. Start server B with fresh state (a different T3 home, so a different environment id) on the same address.
  3. Pair the app with B using a new pairing code.
  4. Open Settings, then Environments.

Expected behavior

Pairing with B replaces the saved entry for that address, or asks whether to replace it. I end up with one working entry.

Actual behavior

The app now has two entries for http://127.0.0.1:13774. B's is connected. A's stays failed with:

Connection failed. Reason: Connected environment b6012475-01fd-418e-b309-2464a6fa9a60 does not match 5bc69f6d-c30f-4899-89c0-cfd9e12222d8.

It never retries, nothing suggests what to do, and it stays until I delete it by hand (environment detail, expand the connection row, trash, Remove). Each new server on that address adds another stale entry.

Impact

Minor bug or occasional failure

Version or commit

main at efecd3cf8b. Fresh iOS dev client built from main.

Environment

iPhone 17 Pro simulator, iOS 26.5. Two local servers with different T3 homes, run one after the other on port 13774.

Logs or stack traces

None beyond the row error above.

Screenshots, recordings, or supporting files

Left: after B starts, A's entry fails. Right: after pairing B there are two entries for the same address, one connected and one stuck:

Stale entry after re-pairing

Stale entry detail

Cause

Saved environments are keyed only by environment id, and pairing never looks at the URL:

  • preparePairingRegistration (packages/client-runtime/src/connection/onboarding.ts) builds the record from the server's own id.
  • EnvironmentRegistry.register (registry.ts) and registerConnectionInCatalog (storageDocument.ts) look up only that id.
  • So pairing B adds a new entry and leaves A's untouched. A's saved id no longer matches the server on that address, and the check in authorization/service.ts blocks it as a ConnectionBlockedError, which is never retried.

Web uses the same shared registration code, so it should behave the same. I only confirmed that in the code.

When users hit this

The server's id lives in <state dir>/environment-id, so it changes whenever the server's state is new:

  • Reinstalling or wiping T3 home on the same machine and port.
  • A new machine taking over a Tailscale name, MagicDNS name or LAN IP.
  • Two servers taking turns on one port, common with dev worktrees.

A normal update that keeps T3 home keeps the id, so it doesn't trigger this.

  • #15467 (open) lets one environment be reached at several addresses. It only matches routes inside the same environment, so a stale entry under a different id would remain. A fix here touches the same registration code.
  • #10940 (open) is the reverse case: same id, different URL.
  • #13072 (open), "have to re-pair", has an unknown cause and might be this.

Workaround

Delete the stale entry by hand.

Source review on main b5a226e49c: pairing registration still selects the saved environment by environment ID; address matching in EnvironmentRegistry.register is limited to routes within that ID. Replacing the old entry crosses pairing registration and persisted credential/routing-trust cleanup, so this needs maintainer review under the automated triage scope restriction. No runtime reproduction performed and no fix applied. Leaving this open.