You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
One-time pairing codes typed with ASCII hyphens or spaces are rejected by the server with 401 auth_invalid, while the same code without separators works. This addresses the separator-rejection part of #4889. The case-only fix in #22089 is already merged; the separate Android false-timeout symptom is outside this change.
Change
Before the one-time pairing-link lookup, remove whitespace and ASCII hyphens only when the remaining value matches the existing generated-code alphabet and length, then use the existing uppercase canonical form. Exact desktop bootstrap matching happens first. The existing atomic consume, expiry, scope, and proof-key checks continue to apply to the canonical pairing code.
Extend the existing PairingGrantStore tests for formatted codes, invalid lengths/alphabet/separators, mixed raw/formatted concurrent consumption, proof-key and scope rejection, and exact desktop credentials.
Scope and approval
This is a small focused fix for an obvious input-formatting bug in the existing pairing capability. It changes the shared server credential boundary used by OAuth and browser-session pairing, so web, desktop, and mobile clients benefit without client or wire-contract changes. It also applies to remote connections because normalization happens on the destination server.
It does not change Android request timing or claim to close the entire issue. No competing open separator fix was found; #22089 explicitly covered case only.
Verification
Windows x64, Node 26.12.1, locked Effect 4.1.2, base 6577c6b6185167c0e502fa01f9e8e8f2dee6ff25.
Regression added before the source change: from apps/server, vp test run src/auth/PairingGrantStore.test.ts --maxWorkers 1 failed in the four formatted-code cases, with seven controls passing. Failure: UnknownBootstrapCredentialError for an issued valid code.
Fixed selected tests: from apps/server, vp test run src/auth/PairingGrantStore.test.ts src/provider/AgentContextBudget.test.ts --maxWorkers 1 passed all 13 tests in two files.
vp fmt --check apps/server/src/auth/PairingGrantStore.ts apps/server/src/auth/PairingGrantStore.test.ts and vp lint with the same two paths passed.
vp run knip:check:files and vp run knip:check:exports both passed.
vp run --filter ./apps/server generate --check passed.
vp run --cache --parallel --concurrency-limit 1 --filter @t3tools/evals --filter @t3tools/scripts --filter t3 typecheck passed all three affected packages.
vp run check:changed --concurrency 1 was attempted on this branch but blocked before its check stages: the ignored-input Git scan follows Windows pnpm junctions and hits the known traversal problem covered by #32023. The wrapper was stopped and is not claimed passed; its cache was not written. Every selected check was run individually with the same scope. Server tests used their owning package configuration because the root browser-project initialization selects port 62126, in this machine's Windows-excluded range; that separate problem is covered by #24007. No repository configuration or check was weakened.
Real network E2E
Ran the actual server CLI twice, before and after the fix, each with a fresh temporary data directory, isolated home and environment, and a reserved loopback port:
The CLI-issued raw code authenticated an administrator through /oauth/token. The fixture then issued disposable read-only codes through /api/auth/pairing-token and exercised the actual HTTP handlers, persistence, authentication, and WebSocket upgrade. No provider prompt or live T3 state was used. Credentials were kept out of the transcript. Both owned server processes were stopped afterward.
Flow
Before
After
Lowercase code grouped with hyphens, OAuth
401
200 Bearer
Code grouped with spaces, OAuth
401
200 Bearer
Mixed whitespace/hyphens, OAuth
401
200 Bearer
Hyphenated code with real ES256 DPoP proofs
401
200 DPoP
Hyphenated code, browser-session cookie
401
200
Before, retrying each same issued code without separators succeeded, proving the credentials and transport were valid. After, all five formatted exchanges established authenticated sessions and returned 200 from /api/orchestration/shell; all three Bearer flows also minted WebSocket tickets and opened /ws. Reusing the original raw code returned 401 in every flow. Slash separators and an invalid alphabet remained rejected without spending a valid code.
This verifies the actual server pairing workflow over loopback HTTP. Native Android/iOS entry, Tailscale HTTPS transport, and the reported Android false timeout were not exercised; there is no changed visual surface.
Implemented with OpenAI GPT-6 via the Codex harness.
The reason will be displayed to describe this comment to others. Learn more.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.
Macroscope's review found this PR approvable — Pairing codes now accept hyphens and whitespace without changing the underlying secret. Expiration, single-use consumption, proof-key binding and scope checks remain unchanged.
You can add or adjust custom eligibility rules. Learn more.
No actionable comments were generated in the recent review.
Walkthrough
The pairing grant store now accepts issued pairing codes with hyphens or whitespace. Tests cover formatted credentials, concurrent consumption, proof-key checks, and rejection of formatted desktop tokens.
Changes: Pairing code format
Layer / File(s)
Summary
Normalize pairing codes and verify consumption apps/server/src/auth/PairingGrantStore.ts, apps/server/src/auth/PairingGrantStore.test.ts
consume removes hyphens and whitespace before validating issued pairing codes. Tests check formatted credentials, concurrent consumption, proof-key handling, and desktop-token rejection.
pingdotgg/t3code#4889 reports 401 responses for formatted manual pairing codes. This change accepts valid issued codes with hyphens or whitespace.
Suggested reviewers:only21mil
Merge Risk: Minimal · up to 74b10
Formatted pairing codes can be accepted without changing exact desktop credential matching. No merge-blocking issue was established.
Pre-merge checks: 4
Check name
Status
Explanation
Title check
The title clearly describes the change to accept formatted one-time pairing codes.
Description check
The description includes all required sections. It explains the problem, change, scope, verification results, and checks that were not completed.
Linked Issues check
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Finishing Touches
Generate unit tests (beta)
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Tip
🎯 Customize your risk assessments
Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.
✅ Automatic approvals, with your rules
Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.
Reviewing files that changed from the base of the PR and between 6577c6b and 74b1089.
Files selected for processing: 2
apps/server/src/auth/PairingGrantStore.test.ts
apps/server/src/auth/PairingGrantStore.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.
This branch has not been deployed
No deployments
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
size:XS0-9 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.
2 participants
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
One-time pairing codes typed with ASCII hyphens or spaces are rejected by the server with
401 auth_invalid, while the same code without separators works. This addresses the separator-rejection part of #4889. The case-only fix in #22089 is already merged; the separate Android false-timeout symptom is outside this change.Change
Before the one-time pairing-link lookup, remove whitespace and ASCII hyphens only when the remaining value matches the existing generated-code alphabet and length, then use the existing uppercase canonical form. Exact desktop bootstrap matching happens first. The existing atomic consume, expiry, scope, and proof-key checks continue to apply to the canonical pairing code.
Extend the existing
PairingGrantStoretests for formatted codes, invalid lengths/alphabet/separators, mixed raw/formatted concurrent consumption, proof-key and scope rejection, and exact desktop credentials.Scope and approval
This is a small focused fix for an obvious input-formatting bug in the existing pairing capability. It changes the shared server credential boundary used by OAuth and browser-session pairing, so web, desktop, and mobile clients benefit without client or wire-contract changes. It also applies to remote connections because normalization happens on the destination server.
It does not change Android request timing or claim to close the entire issue. No competing open separator fix was found; #22089 explicitly covered case only.
Verification
Windows x64, Node 26.12.1, locked Effect 4.1.2, base
6577c6b6185167c0e502fa01f9e8e8f2dee6ff25.apps/server,vp test run src/auth/PairingGrantStore.test.ts --maxWorkers 1failed in the four formatted-code cases, with seven controls passing. Failure:UnknownBootstrapCredentialErrorfor an issued valid code.apps/server,vp test run src/auth/PairingGrantStore.test.ts src/provider/AgentContextBudget.test.ts --maxWorkers 1passed all 13 tests in two files.vp fmt --check apps/server/src/auth/PairingGrantStore.ts apps/server/src/auth/PairingGrantStore.test.tsandvp lintwith the same two paths passed.vp run knip:check:filesandvp run knip:check:exportsboth passed.vp run --filter ./apps/server generate --checkpassed.vp run --cache --parallel --concurrency-limit 1 --filter @t3tools/evals --filter @t3tools/scripts --filter t3 typecheckpassed all three affected packages.vp run check:changed --concurrency 1was attempted on this branch but blocked before its check stages: the ignored-input Git scan follows Windows pnpm junctions and hits the known traversal problem covered by #32023. The wrapper was stopped and is not claimed passed; its cache was not written. Every selected check was run individually with the same scope. Server tests used their owning package configuration because the root browser-project initialization selects port 62126, in this machine's Windows-excluded range; that separate problem is covered by #24007. No repository configuration or check was weakened.Real network E2E
Ran the actual server CLI twice, before and after the fix, each with a fresh temporary data directory, isolated home and environment, and a reserved loopback port:
The CLI-issued raw code authenticated an administrator through
/oauth/token. The fixture then issued disposable read-only codes through/api/auth/pairing-tokenand exercised the actual HTTP handlers, persistence, authentication, and WebSocket upgrade. No provider prompt or live T3 state was used. Credentials were kept out of the transcript. Both owned server processes were stopped afterward.Before, retrying each same issued code without separators succeeded, proving the credentials and transport were valid. After, all five formatted exchanges established authenticated sessions and returned 200 from
/api/orchestration/shell; all three Bearer flows also minted WebSocket tickets and opened/ws. Reusing the original raw code returned 401 in every flow. Slash separators and an invalid alphabet remained rejected without spending a valid code.This verifies the actual server pairing workflow over loopback HTTP. Native Android/iOS entry, Tailscale HTTPS transport, and the reported Android false timeout were not exercised; there is no changed visual surface.
Implemented with OpenAI GPT-6 via the Codex harness.