[Bug]: Android manual pairing rejects formatted codes and can report a false timeout
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
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 (not currently listed in the issue template's Area options)
Steps to reproduce
- Run a headless T3 server and expose it through Tailscale Serve HTTPS.
- Create a one-time pairing credential. The CLI emits an uppercase, unhyphenated 12-character value.
- In the official Android app, open Add Environment.
- Enter the reachable HTTPS backend in Host.
- Enter the credential in the hyphenated format suggested by the field placeholder (for example,
XXXX-XXXX-XXXX). - Tap Add environment.
- Retry with the raw unhyphenated code.
- As a control, paste the complete
https://<host>/pair#token=<raw-token>URL into Host, leave Pairing code empty, and submit.
Expected behavior
The manual pairing-code path should work reliably.
Either:
- normalize presentation separators/whitespace and case before exchange, or
- show the exact server-generated format and clearly require it.
The surfaced error should also match the actual failed request.
Actual behavior
- Hyphenated manual entry is sent verbatim and the server returns
401 auth_invalid. - A retry with raw code also failed during this reproduction.
- The app subsequently reported a timeout after
10000ms, despite the corresponding server-side/oauth/tokenrequest returning401in approximately 2.7ms. - Pasting the complete pairing URL into Host with Pairing code empty succeeded immediately.
The backend and transport were independently verified: environment discovery returned 200, Tailscale connectivity was healthy, and a disposable server-generated credential exchanged successfully through the same public HTTPS /oauth/token endpoint.
Impact
Major degradation or frequent failure.
The advertised manual host + pairing-code flow is unreliable. QR/full-URL pairing remains a workaround.
Version or commit
- Server:
t3 v0.0.32-nightly.20260729.951 - Mobile: official Android client installed from Google Play on 2026-07-29
Environment
- Device: Pixel 9a
- Client OS: Android
- Server OS: Linux x64
- Transport: Tailscale Serve HTTPS on port 443
- Mobile HTTP user agent observed server-side:
okhttp/4.9.2
Logs or stack traces
GET /.well-known/t3/environment -> 200
POST /oauth/token -> 401 (about 2.7ms)
Mobile UI -> timed out after 10000ms
Control:
POST /oauth/token with a disposable server-generated credential through
the same Tailscale HTTPS endpoint -> 200
Secrets, pairing credentials, environment IDs, trace IDs, and Tailnet hostnames have been omitted.
Relevant source observation
The Android pairing field currently shows the placeholder abc-123-xyz, while buildPairingUrl() only applies .trim() to the code before putting it into the URL fragment. It does not remove presentation separators or normalize case.
Relevant files:
apps/mobile/src/features/connection/ConnectionsNewRouteScreen.tsxapps/mobile/src/features/connection/pairing.ts
Workaround
Paste the complete server-generated /pair#token=... URL into the Host field, leave Pairing code empty, then add the environment. Scanning the server-generated QR code should follow the same successful path.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with apps/mobile/src/features/connection/ConnectionsNewRouteScreen.tsx and apps/mobile/src/features/connection/pairing.ts, focusing on buildPairingUrl() and the manual pairing request and error path. Reproduce the hyphenated and raw-code cases against the documented endpoint, then verify that accepted formatting reaches the server correctly and a fast 401 is not surfaced as a 10000ms timeout.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, typescript
- Domain
- authentication, mobile
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 75/100