fix(server): end obsolete link cards after a matching retry succeeds#26944
Problem
Linking a machine through a second request leaves an earlier matching card waiting. The thread continues to show Input even after the machine is linked.
Reproduced on T3 Code 0.1.4: ask to link Box by hint, then issue a new request with Box's address and the same hint and suggested access. Complete the second request. The first card remains pending. Read-only inspection confirmed both records; the existing lifecycle suite did not cover this ordering.
Change
When a link succeeds, end older pending cards in that thread that requested the same machine and access. Match explicit environment IDs first, explicit URLs exactly, or a targetless card's nonblank hint against both the new hint and linked machine label. Preserve unrelated, newer, failed, and different-access requests.
Keep answers already in progress open for their own outcomes. A short lifecycle gate coordinates validation and the protected-card snapshot; network linking remains outside it. The successful outcome and obsolete-card cancellations commit together, without extra agent notifications. The protection field is internal and is not stored on cards or projected to clients.
Scope and approval
This is a focused correction to an existing successful link leaving obsolete matching input behind. It adds no public RPC, user setting, or new workflow, and qualifies for CONTRIBUTING.md's focused obvious-bug exception. No maintainer approval is claimed.
Searched open and closed issues and PRs. #25806 / #26435 concern older async questions becoming inaccessible, rather than link-card cleanup. #26143 clarifies idempotent retries using the same request ID and credential; this reproduction uses distinct requests when supplying a corrected address.
Verification
- Regression test failed before the fix: the old card was pending/waiting after the newer card linked. After the fix it is cancelled and both shell projection paths report no pending input.
- A Deferred-controlled regression holds an older real link while a newer answer grants different access. Both outcomes are reported and the final stored access matches the last operation. It failed before the concurrency correction.
vp test run apps/server/src/peer/PeerLinkRequests.test.ts: 24 passed, including the original 18 tests and six additions. The tests use the real orchestrator, a served peer, pairing and OAuth, with isolated databases.- Server and contracts
tsc --noEmit: passed. Changed-file lint/format, commit-hook knip checks andgit diff --check: passed. - Independent GPT-6.1-Sol review: no remaining actionable findings on
be3263a9c1; independently reran all 24 tests. - No web, desktop or mobile client rendering pass was run. No client code changed; tests exercise the persisted card lifecycle and both sidebar shell derivations shared by the clients. No live conversation database was modified to verify the fix. This does not migrate cards left pending by previously completed links.
Implementation: Claude Opus 5.5 via Claude Code in T3 Code; reviews by GPT-6.1-Sol via Codex in T3 Code.
Checks
Added 2 commits
Approvability
Verdict: Approved at be3263a
Macroscope's review found this PR approvable — Successful retries now close older matching link-request cards in the same thread while preserving answers already in progress. The change is limited to request lifecycle bookkeeping and does not alter pairing permissions or generate additional agent notifications.
You can add or adjust custom eligibility rules. Learn more.
No actionable comments were generated in the recent review.
External image Recent review info
-
External image Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: main
- File: .coderabbit.config.ts
- Commit: 80a2351
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Advanced
- Run ID:
475fe3ca-c51f-4393-8c32-75d01fff1510
External image Commits
Reviewing files that changed from the base of the PR and between be3263a9c159b877be141bc76ad0f638a8478642 and ed7d15448813ed121484dd833aa2d8e96102a3a7.
External image Files selected for processing: 3
apps/server/src/orchestration-v2/Orchestrator.tsapps/server/src/peer/PeerLinkRequests.test.tspackages/contracts/src/orchestrationV2.ts
Limit details: You’ve used all 10 included reviews currently available.
Walkthrough
Link-request answers now pass active answer IDs to orchestration. After a link succeeds, orchestration cancels older pending cards that match the linked machine, except cards still being answered. Tests cover matching rules and concurrent answers.
Changes: Link-request lifecycle
Layer / File(s) Summary Answer coordination
packages/contracts/src/orchestrationV2.ts,apps/server/src/peer/PeerLinkRequests.tsThe record command can carry IDs for cards still being answered. answeruses a per-thread lifecycle lock to validate the card and record the outcome with active answer IDs, while retaining the per-card lock.Matching and cancellation
apps/server/src/orchestration-v2/Orchestrator.tsOrchestration matches cards by requested access and, in order, environment ID, URL, or normalized hint. After a link succeeds, it cancels matching older pending cards that are not still being answered. Lifecycle tests
apps/server/src/peer/PeerLinkRequests.test.tsTests cover matching and nonmatching cards, failed links, overlapping answers, and late answers to cards already ended by a newer link. Estimated code review effort: 3 (Moderate) | ~25 minutes
Change: Bug fix
Sequence Diagram(s)
mermaid1 sequenceDiagram 2 participant PeerLinkRequests.answer 3 participant PeerLinks 4 participant Orchestrator 5 PeerLinkRequests.answer->>PeerLinks: Link requested machine 6 PeerLinks-->>PeerLinkRequests.answer: Return link outcome 7 PeerLinkRequests.answer->>Orchestrator: Record outcome and active answer IDs 8 Orchestrator->>Orchestrator: Cancel older matching pending cards not being answered Possibly related PRs
- pingdotgg/t3code#26143: Its retry identity rules determine whether a link request reuses a card or creates a separate pending card, which relates to this PR’s handling of older cards.
Suggested labels:
bugfixSuggested reviewers:
juliusmarminge,t3dotgg
Merge Risk: External image Minimal · up to ed7d1
No actionable issue is established for this change; it is ready for normal merge checks.
Pre-merge checks: Passed 4
Check name Status Explanation Title check Passed The title is concise, specific, and accurately describes ending obsolete link cards after a matching retry succeeds. Description check Passed The description includes complete Problem, Change, Scope and approval, and Verification sections. It explains the reproduction, implementation, scope rationale, tests, observed results, and limitation… Linked Issues check Passed Check skipped because no linked issues were found for this pull request. Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request. Full details: Description check
Explanation
The description includes complete Problem, Change, Scope and approval, and Verification sections. It explains the reproduction, implementation, scope rationale, tests, observed results, and limitations.
Finishing Touches
Generate unit tests (beta)
- Create a new PR
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Usage-based review receipt
- Mode: Continue automatically
- Reviewed files: 3
- Charged: $0.75
- View usage details
Note
This review was completed with usage-based billing: files reviewed beyond your plan's included limits are billed at $0.25/file. View usage-based billing.
Added 2 commits
Note
Written by gpt-6.1-sol on behalf of Maria
updated the original branch at ed7d15448813ed121484dd833aa2d8e96102a3a7, preserving stevan-borus's two commits and merging current main without rewriting history. the original matching rules and overlapping-answer protection remain intact. trimmed descriptive comments and a duplicate late-answer assertion, keeping all six regression cases and 24 lifecycle tests. the diff against main is now 524 additions and 86 deletions across four files.
verification:
- reproduced the regression with current main's implementation: a successful retry left the older card pending/waiting.
- blacksmith: all 24 peer-link lifecycle tests pass.
vp run check:changed github/mainpasses format, lint, both knip passes, graphql, 35-package typecheck, and five related test files. - real web client: two requests with the same hint and requested access, then pairing through the newer card against a served peer using real oauth. main leaves the older card actionable; this head shows "request ended" and "linked box", and the thread reaches done after the replay provider's questions are answered.
- two independent reviews approved this exact head with no findings.
- unverified: native mobile and desktop. existing stale cards from previously completed links are intentionally not migrated.
same request data in both captures; the replay provider supplies the surrounding conversation. the recording masks the pairing code.
all 28 ci checks are green on this head. the automatic review reports no actionable comments. ready for maria's review; nothing merged.
Note
Written by gpt-6.1-sol on behalf of Maria
Verified in a real dev environment
verified unchanged head ed7d15448813ed121484dd833aa2d8e96102a3a7 on this linux boat vm. this pass used real codex gpt-6.1-sol turns through the local proxy, not replay, mocks, fixtures, or directly inserted cards. playwright from the repo dependencies drove /usr/bin/google-chrome.
- installed and ran
github/mainat6d46336476in a separate worktree, then stopped its captured dev process before starting this exact pr head. only one dev server ran at a time. a separate plain t3 server supplied the real peer. - created a project pointing to a real git repository through the app, selected codex
gpt-6.1-sol, and submitted the same prompt on both versions. codex made four actualt3_environment_linkcalls: unrelated hint, same machine with different requested access, original machine-name request, then matching retry with its address. - main reproduced the bug: after completing the newer card through the actual link dialog and peer oauth authorization, it reported linked while the original card still offered "choose machine".
- this head ended the original matching card immediately: "request ended", followed by "linked" for the newer card. codex received and acknowledged the successful-link notification. the running-server
environment listcommand confirmed the peer was reachable with approval-required access. - nearby behavior: unrelated and different-access cards stayed pending. declining them still produced both decline notifications and codex acknowledgments, without removing the successful link. reloading preserved all four outcomes and cleared the sidebar input badge after the two remaining requests were declined.
- blacksmith ran
vp test run apps/server/src/peer/PeerLinkRequests.test.ts: all 24 tests passed. the head has 28 successful ci checks, zero failing or pending. no source change was needed.
matching request data, viewport, peer and real provider on main and pr; model-generated titles and replies vary. the sidebar correctly remains input in the paired captures because the two deliberately unrelated requests are still waiting.
main: successful retry leaves the original matching card actionable
pr: matching original ends while unrelated and different-access cards remain pending
real retry interaction: approve the link and watch the original request end
short server transcript, including read-only persisted card outcomes
Not verified
native ios/android, the electron shell, macos/windows, relay/tunnel connections and other providers were not exercised on this linux vm. overlapping in-flight answers and the remaining matching-rule permutations passed the existing lifecycle tests, but were not separately raced through the browser. no migration of previously stale cards is claimed. nothing merged, rebased, force-pushed or relabeled.