Skip to content

[Bug]: SSH connect takes 25–60 s: every t3 start costs ~5.5 s and each attempt mints a new pairing token #18229

Description

@loutab4k

Summary

Connecting the desktop app to an SSH environment takes 25–60 s on our server, while plain SSH, the shell and the T3 server itself are fast. The cost comes from starting the t3 binary: every start takes about 5.5 s of CPU, and one connect starts it many times.

Measurements (0.0.46-nightly.20261011.2967, Linux x64, 4 vCPU)

  • t3 --version: 5.3–6.4 s wall, about 5.1 s user. A CPU profile shows about 1.3 s compiling and about 2.9 s evaluating the server bundle's top-level code, even for --version.
  • A cold connect: the launch script starts t3 4 times, about 22–28 s before the port forward opens. That is longer than the client's 15 s per-attempt establishment timeout.
  • A reconnect: the client mints a pairing token over SSH (t3 auth pairing create, about 7 s each) on every attempt and uses only the last one. auth_pairing_links shows 9 tokens in one reconnect (13:32:05–13:33:09, about 64 s) and 4 plus 1 in another.
  • Ruled out: sshd options, shell init (0.06 s on the launch path), runtime download (already installed), HTTP (5–21 ms), initial shell snapshot (about 1 s), CPU throttling.

Likely mechanism (from source at ed0f7b9, not confirmed with a desktop trace)

supervisor.ts gives each attempt 15 s and retries with backoff. SshEnvironmentManager.ensureEnvironment holds a per-target lock, and the renderer calls it through Effect.tryPromise without an abort signal. A timed-out attempt keeps its desktop-side call queued on the lock, so the next attempt waits behind it, times out too, and leaves another orphan, until the backoff lets the queue drain.

Requests

  1. Start t3 once per launch on the adopt path: one helper call, or read server-runtime.json and probe the port with sh/curl.
  2. Coalesce concurrent ensureEnvironment calls per target and cancel orphaned calls when an attempt times out; or reuse a valid session instead of minting a pairing token on every attempt.
  3. Lazy-load the server bundle so --version, __ssh-helper and auth pairing create do not evaluate it.

Related: #11287 (reconnect loop opens many short SSH sessions), #15616 (nightly retries SSH harder), #16477 (2 s reuse probe). None of them names the per-start cost combined with per-attempt pairing.

Activity

  1. juliusmarminge commented on Oct 11, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    This is a real bug on current main: a slow SSH host and the connection retry loop amplify each other.

    Confirmed in code (ed0f7b9):

    • packages/client-runtime/src/connection/supervisor.ts budgets 15 s per establishment attempt (CONNECTION_ESTABLISHMENT_TIMEOUT) and retries with backoff.
    • SshEnvironmentManager.ensureEnvironment runs the whole setup (tunnel + optional pairing) under a per-target KeyedLock.
    • The renderer calls it through Effect.tryPromise in apps/web/src/connection/platform.ts with issuePairingToken: true and no abort signal, so a timed-out attempt keeps holding the lock while the next attempt queues behind it.
    • Each successful pass through that path mints a new token via auth pairing create (REMOTE_PAIRING_SCRIPT in packages/ssh/src/tunnel.ts). The launch script also invokes the t3 runner several times (--version, __ssh-helper pick-port / wait-ready / runtime-port) before serve.

    Your cold-start numbers (~5.5 s per t3 start, several starts before the forward opens) fit that path; I did not re-profile the binary here.

    Not a duplicate of #11287 (many short SSH sessions / UFW) or #15616 (Nightly retry aggressiveness on unreachable hosts). Open PRs #10588 (cancel setup on manager shutdown), #17550 (pair with the reused server's CLI version), and #17919 (keep remote agents across reconnects) touch nearby SSH lifecycle code but do not cancel orphaned ensureEnvironment calls on attempt timeout or reuse a valid pairing token across retries.

    In scope for this issue: coalesce/cancel per-target ensureEnvironment when an attempt times out, and avoid minting a fresh pairing token on every retry when a prior grant is still usable. Cutting the adopt-path down to one helper (or server-runtime.json + a shell/curl probe) belongs here too if it is what unblocks the 15 s budget.

    Out of scope / separate: lazy-loading the server bundle so --version / __ssh-helper / auth pairing create skip top-level evaluation — that is an enhancement, not required to close this bug.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions