Repository navigation
fix(client-runtime): paired iPhone requests stop sending stale cookies and show the host's refusal - #195
Merged
Conversation
…d cookies The server prefers a session cookie over Authorization, and iOS fetch (expo/fetch) sends the shared cookie jar unless credentials is omit. A stale cookie for a host would override the paired Bearer token. Requests that carry Authorization now use credentials: omit; cookie-authenticated web requests are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te's timeout When every route check missed its head start, a slow route that reached the environment and was refused ranked below a dead route's timeout, so the app kept retrying and showed a timeout. A refusal from the environment server now counts as an answer; sign-in refusals from T3 Connect keep their ranking. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 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.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
jayleaton
marked this pull request as ready for review
November 6, 2026 03:10
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
On iOS, paired (non–T3 Connect) connections to a Studio host failed off the home Wi-Fi. The host saw the phone's ticket requests on both the tailnet route and the Tailscale Serve route rejected with
401 Session token revoked, but the app showed a descriptor timeout and kept retrying. Two client defects make this worse and hide the cause:Authorization(EnvironmentAuth.selectRequestCredential). On iOS,expo/fetchreplaces globalfetchand sends the sharedHTTPCookieStoragejar unlesscredentials: "omit"is set. Any stale session cookie for the host would turn a valid Bearer request into a 401.This PR does not claim to fix the off-Wi-Fi connectivity itself. The phone's TCP handshakes to the direct tailnet IP stall (SYN in, SYN-ACK out, no ACK). Whether a cookie was on the original 401 requests wasn't recorded. No production app path was found that puts a T3 session cookie into the app's jar.
Summary
The diagnostics commit
b368ca6, which shipped in TestFlight 2.2.0 (14), is included somainmatches the build.Change
packages/client-runtime/src/rpc/http.ts: requests that carryAuthorization(Bearer or DPoP) sendcredentials: "omit", merged into any existingRequestInitsuch ascache: "no-store". WebSocket upgrades are untouched: the server checkswsTicketbefore cookies.packages/client-runtime/src/connection/driver.ts: aConnectionBlockedErrorwithhop: "server"counts as an answered route.Scope and approval
Maintainer-authorized fork fix (JJ) for the iPhone paired-connection investigation. Two small client-runtime changes at existing boundaries; no new behavior surface.
Verification
Failing first, then passing:
packages/client-runtime/src/authorization/layer.test.ts("prepares a cold direct bearer connection…"): Before:expected { method: 'POST', … } to match object { credentials: 'omit', … }. After: passes.packages/client-runtime/src/connection/supervisor.test.ts("reports the environment's refusal from a slow route over a silent route's timeout"): Before: never reachesblocked(times out waiting). After: passes withlastFailure= "The environment credential is invalid."environmentHttpAuth.test.tsassertions that pinnedcredentials: undefinedfor bearer and DPoP requests now expect"omit".Runtime transcript against a real server (
node apps/server/src/bin.ts serveon a throwaway base dir on loopback). It used a real session cookie from/api/auth/browser-session, revoked via the CLI, and a valid CLI-issued Bearer:Native source checked:
expo/src/winter/fetch/fetch.ts(defaultcredentials: "include") andios/Expo/Fetch/ExpoURLSessionTask.swift:28-35(omitleaves out cookies).Focused suites: client-runtime supervisor, resolver, registry, routes, authorization layer and remote, environmentHttpAuth, shell-sync, rpc session (224 passed); mobile tracing and linkEnvironment, web linkEnvironment (pass).
vp run check:changed: format, lint, knip, typecheck (36 packages), tests and react compiler all pass.Not yet verified: real off-Wi-Fi app behavior on a device. Acceptance (direct tailnet and Serve off Wi-Fi, Wi-Fi transition, foreground and resume on a paired connection) needs a TestFlight build from this branch.
Merge Danger
Door: two-way. Revert restores the previous behavior; no persisted state or schema changes.
Blast Radius: consumers. Every web and mobile request with an explicit
Authorizationheader stops sending cookies; web primary-environment cookie auth is unaffected. The connection supervisor now stops in "blocked" (it still retries on foreground, network change or credential change) when any route gets a real refusal from the server, instead of backing off forever behind another route's timeout.Model and harness: Claude Opus 5.5 (Doug) via Claude Code in T3 Code.
🤖 Generated with Claude Code