[Bug]: Pairing-issued sessions still expire after a hard-coded 30 days (#13055 was closed by the pairing-token TTL commit), and an expired direct route silently falls back to T3 Connect#33183

opened2d agobyKKevinXC5

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

Server auth / remote environments (direct routes)

Steps to reproduce

  1. On host A (macOS, T3 Code Nightly desktop), expose the server on a direct route (own domain over IPv6 behind an nginx reverse proxy). T3 Connect is also enabled.
  2. On client B (another Mac running T3 Code Nightly desktop), pair to host A with a pairing link (t3 pair). The environment ends up with a direct route plus the T3 Connect route.
  3. Keep using it normally for 30 days.

Expected behavior

One of the following, as discussed in #13055:

  • the session lifetime of pairing-issued sessions is configurable (e.g. a T3CODE_SESSION_TTL-style setting), or
  • sessions in active use get a sliding expiry / renewal,

and in any case, when the direct route's session expires, the UI says so and offers to re-pair.

Actual behavior

  • #13055 was closed as completed on 2026-10-11, but the commit that closed it (18d07b7, "feat(auth): make pairing token ttl configurable") only added T3CODE_PAIRING_TOKEN_TTL, i.e. the lifetime of the pairing code. Its own message says "Closes part of #13055 (pairing-side ttl knob; session lifetime config is a separate surface)". The session-lifetime PR #13092 was closed without merging.
  • In 0.1.8-nightly.20261109.4392, SessionStore still has const DEFAULT_SESSION_TTL = days(30) with no override and no renewal. t3 auth session list shows expires = issued + 30 days whatever last connected is.
  • When the direct route's bearer session expired, the client silently fell back to T3 Connect. Settings → Environments just showed T3 Connect as "In use" while the direct route looked healthy. The cause only appeared after removing the T3 Connect route: Cause: authentication, Error: The environment credential is invalid. The host logged Rejected authenticated session credential. with reason: "Session token expired." roughly once a minute, and none of that reached the UI.

Impact

Major degradation or frequent failure

Every directly-paired client (desktop ↔ desktop in both directions, plus phones to each machine) hits a hard cliff every 30 days. Users who pick a direct route because T3 Connect is slower for them get quietly downgraded to the relay with no indication of why. Recovery needs shell access to the host to mint a new pairing link. Phones can only redeem pairing codes, so t3 auth session issue --ttl is not a workaround there.

Version or commit

0.1.8-nightly.20261109.4392 (both host and client)

Environment

macOS (arm64) on both machines, T3 Code Nightly desktop app. Host runs the desktop-managed server on :3773, published on a direct route through nginx (WebSocket upgrade configured), with T3 Connect also enabled.

Logs or stack traces

text
1Client (route diagnostics):
2Cause authentication
3Route Direct
4Error The environment credential is invalid.
5
6Host (server.trace.ndjson):
7"Rejected authenticated session credential." {"reason": "Session token expired."}

Workaround

Mint a new pairing token on the host (t3 pair) and open t3code://pair?pairingUrl=<direct-route>/pair#token=… on the client. This lasts another 30 days.

Related: #13055, #13092, #9884, #17014

Synced from GitHub

Same issue on a different setup: a LAN-only connection with no T3 Connect route, so there's no silent fallback. It fails outright with a misleading message.

Host: Fedora VM, T3 Code installed as a systemd user service via t3 service install, at 0.1.8-nightly.20261107.4340 when it broke and then 0.1.8-nightly.20261109.4399. Client: T3 Code Nightly desktop on macOS arm64 (0.1.8-nightly.20261109.4399), connecting over LAN with a direct route only. A second remote host broke the same morning.

What the user sees: the banner says "fedora-vm is offline — T3 Code on fedora-vm did not accept this device", with Reconnect and Disconnect server buttons. Reconnect resends the same expired credential, so it can never succeed. Nothing mentions expiry or suggests re-pairing. Updating both host and client to the latest nightly changes nothing. This comes from packages/client-runtime/src/connection/diagnosis.ts, which maps any authentication/permission failure on the server hop to "did not accept this device", even though the host logged the exact cause.

Host evidence: in auth_sessions, the desktop's last session was issued 2026-10-09T13:05Z, expired 2026-11-08T13:05Z, and last connected 2026-11-08T12:14Z (used 50 minutes before it expired). Every session row has expires_at = issued_at + 30d whatever last_connected_at is. From then on, server.trace.ndjson logs this repeatedly:

1EnvironmentAuth.authenticateHttpRequest → "Rejected authenticated session credential." {"reason":"Session token expired."}
2 ServerAuthInvalidCredentialError: Server authentication credential is invalid. at SessionStore.verify

The paired iPhone (mobile 2.0.0) is on the same clock: last connected yesterday, expires 2026-11-10.

Suggested minimum, even without sliding expiry: surface "session expired" in the client, swap the dead-end Reconnect for a re-pair action, and warn a few days before expiry.

Triaged with Claude Code (Opus 5.5, claude-opus-5-5) via t3 triage.

Synced from GitHub

Thanks for #33199. It covers the second half: the UI now says when a route's sign-in is refused.

For the first half (the session lifetime itself), #31755 by MarcoDotIO looks like a direct fit. It adds T3CODE_PAIRING_SESSION_TTL and applies it only to sessions redeemed from pairing links, with the 30-day default unchanged. That would fix this for phones too, since they can only pair with a code. Could a maintainer take a look at it? Two earlier attempts, #13092 and #25361, were closed without a decision, so it would also help to know if a configurable lifetime is something you'd accept at all.

The comment above shows the same 30-day cliff on a LAN-only host with no relay to fall back to.

Synced from GitHub

Hit this today with the iOS app, across several hosts at once.

Setup: iPhone (T3 Code iOS) paired by code to several hosts on 2026-10-09, all reached over Tailscale Serve (https://<host>.<tailnet>.ts.net). Two are Ubuntu hosts running t3 serve 0.1.7 as a service. The third is a macOS desktop app, also 0.1.7. No T3 Connect route on these.

What happened: On 2026-11-08 both Linux hosts started showing "T3 Code on did not accept this device · Retrying · attempt N" on the phone. Nothing said the sign-in had expired or that I needed to re-pair. I first suspected Tailscale and a reboot. The network was fine: tailscale ping worked, and HTTPS to the serve URL returned 200.

Root cause, from statev2.sqlite → auth_sessions on each host:

1client_label=celular client_os=iOS issued_at=2026-10-09T21:35:40Z expires_at=2026-11-08T21:35:40Z
2client_label=iphone client_os=iOS issued_at=2026-10-09T17:44:24Z expires_at=2026-11-08T17:44:24Z

These are exactly 30 days from issue, even though last_connected_at was the same day on one of them. So the sessions were in daily use and still expired.

Workaround: SSH into each host, run t3 pair --ttl 30m, then open https://<tailnet-url>/pair#token=… on the phone. That works for another 30 days. On a phone, a pairing code is the only way to sign in, so t3 auth session issue --ttl doesn't help.

+1 for #31755 (configurable T3CODE_PAIRING_SESSION_TTL) and/or sliding renewal for sessions in active use. Also +1 for #33199. "did not accept this device" should say that the session expired and offer to re-pair.

Synced from GitHub

Same 30-day cutoff, on two hosts at once, while I was away from both and couldn't reach either. That's the worst version of this bug, so here are the details.

Setup

  • Client: T3 Code Nightly desktop, Linux x64, 0.1.8-nightly.20261105.4234.
  • Hosts: a Windows desktop and a Linux machine, both 0.1.8-nightly.20261107.4301, each added to the client as a remote environment over a direct route (LAN, plus a Tailscale route learned for one). No T3 Connect.

What happened

Both credentials in the client's connection catalog were issued 2026-10-07, about 100 minutes apart, and both expired exactly 30 days later on 2026-11-06. Both were in regular use. Since then every connection attempt fails with The environment rejected this client's credentials (invalid_credential) on POST /api/auth/websocket-ticket/rpc. The UI just says the host "did not accept this device", and Reconnect resends the same expired credential, so it can never succeed.

Why this is critical when you're remote

  • No warning beforehand. Nothing in the app said the sessions were close to expiring, so I couldn't re-pair while I still had access.
  • No explanation afterwards. "Did not accept this device" reads like the host is rejecting this device. Nothing mentions expiry or re-pairing. I lost time chasing network, tunnel and pairing theories before decrypting the client's catalog and finding the exp dates.
  • No way to recover remotely. The only fix is a new pairing link, and that can only be made on the host. If you rely on T3 Code itself to reach your machines remotely, and have no SSH or remote desktop to them, you're locked out until you're physically back at each one. Both of mine expired together because they were paired the same day, so I lost both at once.
  • It happens again every 30 days. Re-pairing just sets the next cutoff. Anyone who pairs several devices in one sitting loses all of them on the same day.

Something that silently locks you out of your own machines on a fixed date, with no warning and no remote recovery, shouldn't be the default for sessions in active use.

What would fix it, in order of impact:

  1. Sliding expiry, or renewal while connected, for pairing-issued sessions. The client already renews DPoP tokens while it's connected; bearer sessions get nothing.
  2. An operator-set lifetime, as in #31755 (T3CODE_PAIRING_SESSION_TTL), at minimum.
  3. A warning in the client a few days before a session expires, and an error that says "session expired, re-pair" instead of "did not accept this device" (#33199 covers part of this).

Two earlier attempts (#13092 and #25361) were closed without a decision on whether a longer or renewing session would be accepted. A maintainer decision on that would unblock #31755.

Investigated with Claude Opus 5.5 in T3 Code, following the triage playbook.

Synced from GitHub

Same 30-day expiry, desktop to desktop over a Tailscale direct route.

Setup

  • Client: T3 Code Nightly desktop, Windows 11 x64, 0.1.9-nightly.20261110.4478
  • Host: T3 Code Nightly desktop, Windows, 0.1.9-nightly.20261110.4452
  • Route: Direct (Tailscale). Devices were never revoked or re-paired.

What happened The client card showed "T3 Code on <host> did not accept this device", with Cause authentication and Error "The environment credential is invalid." Every retry failed the same way. The update to the nightly happened around the same time, which made it look like the update caused it. It didn't: no auth code changed between .4452 and .4478.

Host evidence (trace 71632669e50fca3f47216c47407f98d5)

1EnvironmentAuth.authenticateHttpRequest WARN "Rejected authenticated session credential." reason="Session token expired."

Repeated on every retry from 2026-11-10 21:37 UTC to 2026-11-11 02:03 UTC.

The UI still doesn't say the login expired or that re-pairing is the fix. A user who just updated will blame the update. A configurable or sliding lifetime (#31755) would prevent this.

Investigated with t3 triage by Claude Code (Claude Opus 5.5).