Skip to content

[Bug]: Android T3 Connect creates two authorized sessions per environment connection #7910

Description

@tomcrojo

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

Steps to reproduce

  1. Link two or more T3 Code environments to T3 Connect.
  2. Sign in to the native Android app and let it connect to the linked environments.
  3. On each environment host, open Settings → Connections → Authorized clients.
  4. Observe two identical T3 Connect connect Android clients for the one phone/environment connection.
  5. Close/reopen or resume the Android app after the cached environment access token has expired; the pattern recurs as another pair.

This was reproduced repeatedly across multiple days and on each linked host.

Expected behavior

One Android device connecting to one environment should create one authorized environment session and one RPC/WebSocket connection, matching the documented single connection-runtime ownership model.

Actual behavior

Two distinct T3 Connect connect sessions are created for the same Android device and environment. Both have the same five scopes, DPoP proof-key thumbprint, Android/OkHttp metadata, and one-hour lifetime.

This is not only a duplicated UI row. Read-only inspection of the environment database and server traces shows two independent end-to-end flows:

  • two /api/t3-connect/mint-credential requests
  • two environment descriptor requests
  • two /oauth/token exchanges
  • two /api/auth/websocket-ticket requests
  • two actual /ws connections

For one measured occurrence:

  • pairing links were created 5 ms apart
  • access-token sessions were issued 35 ms apart
  • both pairing links were consumed by the same Android DPoP key

Historical session rows show the same paired issuance pattern repeatedly, generally tens of milliseconds apart.

Impact

Minor bug or occasional failure

The app remains usable, but each Android connection doubles authorized-client rows and briefly opens duplicate WebSockets. Revoking one row is not a durable workaround because the pair can be recreated on a later bootstrap.

Version or commit

Environment server: 0.0.34-nightly.20260817.1120

Android app: installed Android T3 Connect build current as of 2026-08-22; exact client build is not exposed in the server-side metadata for this version.

Current main was also inspected for the relevant connection/auth flow.

Environment

  • Native T3 Code Android app (okhttp/4.9.2)
  • T3 Connect managed relay / Cloudflare tunnel
  • Linux and macOS linked environment hosts
  • Same behavior on each linked host

Logs or stack traces

# Sanitized shape from auth_sessions for one occurrence:
issued_at                  client_device_type  client_os  client_user_agent  scopes
2026-08-22T15:54:36.149Z   mobile              Android    okhttp/4.9.2       5 standard scopes
2026-08-22T15:54:36.184Z   mobile              Android    okhttp/4.9.2       5 standard scopes

# Corresponding sanitized pairing-link timing:
created_at                 consumed_at                proof_key
2026-08-22T15:54:35.708Z   2026-08-22T15:54:36.182Z  same Android key
2026-08-22T15:54:35.713Z   2026-08-22T15:54:36.147Z  same Android key

All session IDs, hostnames, IPs, tokens, proofs, and key material have been omitted.

Investigation notes

The observed race appears compatible with two concurrent calls to RemoteEnvironmentAuthorization.authorizeDpop. In packages/client-runtime/src/authorization/service.ts, authorization reads the per-environment token store, then obtains a bootstrap credential, exchanges it, and only afterward persists the token. There is no visible per-environment single-flight/critical section around that read-mint-write sequence, so two concurrent connection attempts can both observe a miss and mint separate sessions.

That explains how the duplicates survive server-side, but not why Android initiates two connection attempts. The server traces have distinct propagated trace IDs for the two complete flows, indicating separate client-side attempts rather than duplicate rendering or a server retry.

Potential hardening:

  1. Identify the duplicate Android startup/resume connection owner or signal.
  2. Single-flight authorizeDpop per environment/device so concurrent cache misses share one bootstrap exchange.
  3. Consider server-side replacement/deduplication for an active cloud-connect session with the same DPoP thumbprint and client identity.
  4. Include mobile app version/surface in authorized-session metadata to improve diagnostics.

Workaround

Revoking one duplicate while the Android app is closed removes the extra row temporarily. It may be recreated when the app next needs a fresh environment access token.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions