fix(client-runtime): re-pairing refreshes saved route logins#24774

closed1
main
GitHub fork: fix/re-pair-direct-route-credentials-v5
opened17d agobyMmaxwellyoung
View on GitHubTwo-way sync enabledPR last imported 10d ago

Re-pairing a saved environment at another address leaves its older direct routes using the invalid login. Removing the new route then locks the environment out. This fixes the credential ownership paths confirmed in #17014's triage, points 1 and 2.

Write the replacement login to the environment's direct bearer owners in the existing catalog mutation. Unchanged-token profile edits keep other credentials intact. Learned routes borrow from those owners; SSH and T3 Connect retain their separate authentication. When the same address replaces a legacy owner ID, reuse the existing removal cascade for its learned routes.

Refs #17014. This addresses its stale direct-route credentials; error presentation and retry priority are outside this change.

Verification on main at 242d00cd3453:

  • Two regression tests fail before the fix, with 54 controls passing. With the fix, all 90 focused tests pass across the catalog, registry, resolver, routes, and web/mobile storage adapters.
  • vp run check:changed 242d00cd34534e7a93cb6f6b02cf4652bdd1210e passes format, lint, knip, affected types and adjacent tests. Scoped vp check passes, and vp run typecheck passes all 33 packages.
  • In the normal web UI, pair localhost to a second isolated T3 server, revoke its session through the auth CLI, and pair 127.0.0.1 as a new route to the same server. Removing that new route makes main's remaining localhost route receive a websocket-ticket 401 and show the rejected-device error. With the fix loaded, re-pairing refreshes the retained route; it stays connected after removal and reconnects after a full reload.

Both servers use production pairing, session storage and WebSocket authentication. Revocation models an invalid session; this is not a 30-day expiry test. The two addresses are loopback aliases, not a LAN or Tailscale transition. No packaged desktop or native mobile run is claimed, and no provider turn was needed.

Before:

Retained route rejects the saved credential

After removing the new route and reloading:

Retained route reconnects with the refreshed login

Removal and reload recording

Model and harness: gpt-6.1-sol through Codex in T3 Code.

Checks

22 successful · 12 skipped · 1 neutral
All checks passed
dbb90e0

Added 1 commit

  • fix(client-runtime): re-pairing refreshes saved route loginsdbb90e0
Synced from GitHub

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — This fixes stale saved logins during re-pairing and updates credential ownership across direct bearer routes, with route cleanup for dependent learned addresses. Because it changes production authentication and credential persistence behavior, the authentication-sensitive criteria call for human review.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between b3d382e1009d5430de8c528aef99a39854a7cbc4 and dbb90e00937c160548c1c1349f5659520e11b576.

External image Files selected for processing: 4
  • packages/client-runtime/src/connection/registry.test.ts
  • packages/client-runtime/src/connection/registry.ts
  • packages/client-runtime/src/platform/storageDocument.test.ts
  • packages/client-runtime/src/platform/storageDocument.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Walkthrough

Route registration now replaces a same-address route when its ID differs, while retaining learned routes. Bearer registration refreshes credentials for eligible routes in the same environment when the token changes. SSH registration retains its separate update behavior.

Changes: Route re-pairing

Layer / File(s)Summary
Same-address route replacement
packages/client-runtime/src/connection/registry.ts, packages/client-runtime/src/connection/registry.test.ts
Registration removes a same-address route when its route ID differs from the incoming route’s ID. Tests check that learned routes remain stored and available as alternate routes.
Bearer credential registration
packages/client-runtime/src/platform/storageDocument.ts, packages/client-runtime/src/platform/storageDocument.test.ts
Bearer registration updates its target credential and, when the token changes, updates eligible bearer routes in the same environment. Tests cover route removal, schema round-tripping, preservation of unrelated records, and profile edits.

Priority: External image Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • pingdotgg/t3code#23367 — This change updates bearer credentials and tests learned-route preservation, which overlap with the issue’s saved route ownership concerns.
  • pingdotgg/t3code#19398 — This change refreshes credentials during re-pairing, but does not discover or update changed host:port endpoints.

Possibly related PRs

  • pingdotgg/t3code#19358 — Both changes address route registration, same-address handling, and learned routes for saved environments.
  • pingdotgg/t3code#24588 — Both changes involve LAN routes and learned direct routes in the client.

Suggested reviewers: maria-rcks

Merge Risk: External image Minimal · up to dbb90

No actionable issue remains in the reviewed route re-pairing changes; they are ready to merge after normal checks.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title "fix(client-runtime): re-pairing refreshes saved route logins" follows conventional commit format and names the main change: re-pairing a saved environment now refreshes the stored direct be…
Description checkPassedThe description follows the template's main sections. Problem: re-pairing at another address leaves older direct routes with an invalid login, which can lock the environment out. Change: the replaceme…
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title "fix(client-runtime): re-pairing refreshes saved route logins" follows conventional commit format and names the main change: re-pairing a saved environment now refreshes the stored direct bearer credentials that learned routes borrow. The changeset matches this: registry.ts and storageDocument.ts update bearer credentials on re-pair, and the tests cover that behavior. The title is specific and concise, and a teammate scanning history would understand the primary change.

Full details: Description check

Explanation

The description follows the template's main sections. Problem: re-pairing at another address leaves older direct routes with an invalid login, which can lock the environment out. Change: the replacement login is written to direct bearer owners in the catalog mutation, unchanged-token edits keep other credentials, learned routes borrow from owners, SSH and T3 Connect keep separate auth, and legacy owner IDs reuse the removal cascade. Scope and approval: it links the triage comment on #17014 and states which points (1 and 2) it covers and what is out of scope. Verification: it gives specific test counts, the commands run, the before/after results, and the manual web UI scenario, with screenshots and a recording. It also names the model and harness used. The Scope section cites the triage comment rather than an explicit maintainer approval comment, and the 'Problem' section does not give a formal reproduction list, but the description is mostly complete.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified as-is at dbb90e00937c160548c1c1349f5659520e11b576 on this linux boat vm. no code changes or pushes were needed.

installed dependencies with vp i and ran one background dev server at a time. used the repo's playwright and /usr/bin/google-chrome against the real web app, configured as a hosted client so a primary cookie connection could not mask saved-route failures. used isolated empty server state, production pairing/session revocation, and a real git workspace added through the app. no mocks, fixtures, replay provider, or demo data.

  • on github/main at 15fe2832727893cbdd2214383f9f971474849695, paired http://localhost:16733, created a project/thread, and completed a real codex gpt-6.1-sol turn. revoked that bearer session with the auth cli, added http://127.0.0.1:16733 through the environment's add route control, then removed that new route. the retained localhost route returned websocket-ticket 401 and showed the rejected-device error, including after reload.
  • repeated that flow on the exact pr head. removing the new route and fully reloading kept localhost connected, with websocket-ticket and orchestration requests returning 200. sent a follow-up through the retained route: codex ran git status --short successfully and replied retained route works.
  • neighboring behavior: switching the environment off and on reconnected successfully. pairing localhost again retained exactly one route and survived reload. the existing project and conversation remained accessible.
  • blacksmith ran vp test run packages/client-runtime/src/platform/storageDocument.test.ts packages/client-runtime/src/connection/registry.test.ts: 56 tests passed. all 28 existing ci checks are successful on the unchanged head.

short runtime transcript, with credentials omitted:

text
1main, after removing replacement route and reloading:
2POST /api/auth/websocket-ticket/rpc -> 401
3T3 Code on box-node-99a339356cc51c66 did not accept this device
4
5pr, after removing replacement route and reloading:
6POST http://localhost:16733/api/auth/websocket-ticket/rpc -> 200
7GET http://localhost:16733/api/orchestration/shell?omitIdleSubagents=1 -> 200
8codex.app-server turn/start: model=gpt-6.1-sol cwd=/home/user/repair-verification
9commandExecution: /bin/bash -lc 'git status --short', exitCode=0
10turn/completed: completed
11client response: retained route works.

matching scenario and viewport below; main and the pr have different settings layouts because they are different revisions. inspected the full-size captures and recording, and checked uploaded playback and seeking.

main: retained localhost route rejects the device after replacement route removal and reload

pr: retained localhost route is connected after replacement route removal and reload

pr: remove the replacement route, reload, and reconnect through localhost

pr: real codex follow-up completes through the retained route

Not verified

native ios/android, packaged electron, macos/windows, ssh, and t3 connect authentication were not exercised on this linux web run. the addresses were loopback aliases, so this does not prove lan/tailscale transitions or https transport. revocation simulated an invalid saved login; this was not a 30-day expiry test. legacy route-id cleanup and unchanged-token edits have existing test coverage but were not recreated through a legacy client. no performance claim is made.

Note

🤖 GPT-6.1 Terra [1m] responding on behalf of Theo

Closing in favor of the credited maintainer replacement, which preserves login refresh and legacy-owner cleanup and also protects secure pairing tokens from plain HTTP routes. #25807 #17014