fix(server): reject pairing with an incompatible server protocol#23789

merged2
main
fix/issue-15492
opened5d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 3d ago

Problem

Running t3 pair against a server on another orchestration protocol prints a token that the server cannot use. For example, stable 0.0.45 writes state.sqlite while a V2 server reads statev2.sqlite; the reverse direction also creates an unnecessary V2 database before returning an unusable token.

Change

Check the discovered server's orchestration protocol before Tailscale setup or database/auth initialization. Refuse mismatches with both protocol versions and the exact server-version pairing command. Missing protocol metadata uses the existing V1 compatibility default; matching protocols retain the existing pairing path.

Scope and approval

Fixes https://github.com/pingdotgg/t3code/issues/15492

Triaged bug via the automated issue flow: #15492. Limited to the pairing CLI compatibility check and its regression test.

Verification

  • Red test on d90dd6ee07: expected only server-runtime.json; actual also contained environment-id, secrets, and statev2.sqlite.
  • vp test run apps/server/src/cli/pair.test.ts: 14/14 passed on c502579298.
  • Scoped lint and format passed; server typecheck passed. Latest main was already contained in the branch.
Real server/CLI checkResult
Published stable CLI → current V2 serverBefore: token printed, fresh exchange HTTP 401
Current CLI → published stable V1 serverBefore: token printed, fresh exchange HTTP 401; after: exit 1, actionable mismatch, no token, SQLite/sidecar hashes unchanged
Fixed current CLI → current V2 serverToken issued, fresh exchange HTTP 200

issue-15492-runtime.md

Independent sanitized evidence audit

Server/CLI-only change; render matrix empty. Runtime verified on Linux x64, Node 26.12.1, isolated real V1/V2 servers. macOS desktop and Tailscale wrapping were not run. The already-published immutable 0.0.45 package remains affected; the source fix protects future CLI releases.

Models: Claude Opus 5.5 (Claude Agent harness), GPT-6.1-Sol (Codex harness).

Verification log

Review + omitted-protocol evidence at c502579298, against merge-base 044f07257c. No source changes or fixes applied; these are delegated review results, not posted forge reviews.

  • Reviewer A — Codex, gpt-6.1-sol: MERGE. Full diff and discovery/config/auth paths traced: guard precedes database/migrations, secrets, environment-id and Tailscale writes; failed/unreachable discovery cannot reach token minting. No pairing acceptance, scope or grant changes. Static review; diff check passed.
  • Reviewer B — Claude Agent, claude-opus-5-5: MERGE. Full diff and descriptor history traced: omitted metadata uses the established V1 default; production V2 producers always include the field. Focused pairing suite passed 14/14. Nonblocking caveats: retain original CLI flags with the suggested command; unpublished server versions may not have a matching npm release. No auth acceptance/grant change beyond refusing unusable token issuance, so neither reviewer triggered NEEDS_JULIUS.
Additional evidence rowBefore (merge-base)After (head)
Controlled HTTP descriptor server omits orchestrationProtocolVersion; real source CLI, isolated fresh and existing homesExit 0, token printed. Fresh: creates environment-id, secrets/server-signing-key.bin, statev2.sqlite, one pairing row. Existing: database hash changes, pairing rows 1→2.Exit 1, no token; reports server protocol 1 / CLI protocol 2 and npx t3@0.0.45 pair. Fresh: no files created or changed. Existing: all file hashes unchanged, pairing rows remain 1.

Sanitized transcripts, file inventories, and exact driver. Driver SHA-256: 4c008957f981f4a2cb7812cd5067f23fa2ccf8b34861975e6e26a5ceb96e1226.

This row uses a descriptor fixture advertising 0.0.45, not a full production server; no exchange result is claimed. All runtime homes were under /tmp/ev-23789; no real T3 database was accessed. No Tailscale command was run. Server port-owner PIDs were recorded and their cwd checked before stopping. An initial Vite+ shim bootstrap was discarded and the row rerun using the installed Node binary directly. Missing metadata on a hypothetical modified V2 server would also be treated as V1; shipped V2 descriptor constructors emit the field. No correction to the existing PR-body evidence claims was found.

Checks

21 successful · 16 skipped · 1 neutral
All checks passed
c502579

Added 2 commits

  • fix(server): t3 pair refuses a server on another orchestration protocolc502579
  • test(server): reproduce issue 15492, t3 pair mints into an incompatible server's databased90dd6e
Synced from GitHub

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — This narrowly scoped fix rejects protocol-incompatible servers before pairing setup can initialize authentication storage or mint an unusable credential, while preserving matching and legacy paths. Because it changes authentication-related runtime behavior, human review is warranted.

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

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB−7 B (−0.1%)6.8 KiB✅
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
CodexLive turn WebSocket wire1.2 KiB1.2 KiB−7 B (−0.6%)2.0 KiB✅
CodexLive turn WebSocket decoded20.9 KiB20.9 KiB0 B (0.0%)29.3 KiB✅
CodexLive turn messages220 (0.0%)8✅
ClaudeTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
ClaudeLive turn WebSocket decoded21.2 KiB21.2 KiB0 B (0.0%)29.3 KiB✅
ClaudeLive turn messages220 (0.0%)8✅

Baseline: 4eae8ae · PR result: c502579 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
📥 Commits

Reviewing files that changed from the base of the PR and between 044f07257c9684ae4543bd1e1ebf9c394e9e3830 and c502579298f719c4346f48ff75f3b7d896ba17c4.

📒 Files selected for processing: 2
  • apps/server/src/cli/pair.test.ts
  • apps/server/src/cli/pair.ts

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

📝 Walkthrough

The pairing CLI now checks the discovered server’s orchestration protocol version before continuing with pairing setup. It returns an incompatibility error for a mismatch. A regression test verifies that this failure does not create additional userdata files.

Changes: Pairing protocol compatibility

Layer / File(s)Summary
Protocol compatibility guard
apps/server/src/cli/pair.ts
pairCommand compares the discovered server’s protocol version with the CLI’s version. If they differ, it returns IncompatibleServerProtocolError with the server version and protocol version. A missing server protocol version defaults to 1.
Mismatch regression test
apps/server/src/cli/pair.test.ts
The test descriptor helper accepts a supplied descriptor. The regression test checks that a protocol mismatch fails and leaves only the existing server-runtime.json file in the userdata directory.

Priority: ➖ Normal

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

Change: Bug fix · Severity of issue fixed: Medium

Possibly related issues

  • pingdotgg/t3code issue 22235 — The PR rejects a CLI/server protocol mismatch, but does not address duplicate servers or stale endpoint routing.

Suggested reviewers: t3dotgg

Merge Risk: ⚪ Minimal · up to c5025

The compatibility check rejects mismatched servers before pairing changes local state. No merge-blocking issue was found.

Pre-merge checks: Passed 4
Check nameStatusExplanation
Title checkPassedThe title clearly and concisely states the main change: rejecting pairing when the server protocol is incompatible.
Linked Issues checkPassedIssue #15492 requires the CLI to reject a protocol mismatch before database access and provide an actionable matching-version command. The reviewed source adds IncompatibleServerProtocolError, which…
Out of Scope Changes checkPassedThe changes are limited to the pairing protocol check and its regression test, as requested by issue #15492. The descriptor helper updates and protocol fields support that test. No unrelated changes a…
Description checkPassedThe description follows the required template. It explains the problem, change, scope and approval, focused verification results, limitations, and agent attribution. It also documents the linked issue…
Full details: Linked Issues check

Explanation

Issue #15492 requires the CLI to reject a protocol mismatch before database access and provide an actionable matching-version command. The reviewed source adds IncompatibleServerProtocolError, which reports both protocol versions and npx t3@<serverVersion> pair. The supplied change summary places the check before server configuration and token creation. The regression test uses a protocol 1 server descriptor and checks failure, the versioned command hint, and that no userdata files beyond server-runtime.json are created. Missing protocol metadata retains the V1 default, and matching protocols retain the existing path.

Full details: Out of Scope Changes check

Explanation

The changes are limited to the pairing protocol check and its regression test, as requested by issue #15492. The descriptor helper updates and protocol fields support that test. No unrelated changes are evident in the reviewed summary or test diff.

Full details: Description check

Explanation

The description follows the required template. It explains the problem, change, scope and approval, focused verification results, limitations, and agent attribution. It also documents the linked issue and relevant test evidence.

✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • 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.