SSH reconnects can accumulate stale local-forward processes
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
Summary
T3 Code 0.0.28 can accumulate stale SSH local-forward processes when a saved SSH environment repeatedly reconnects across sleep, network transitions, or temporarily slow readiness responses.
In a redacted production observation, one desktop instance owned 11 forwards for the same remote backend: 10 still answered and one was dead. The backend's worker-local readiness response was approximately 1 ms, while the same response through existing forwards intermittently took 2.6–5 seconds. The backend remained healthy and externally owned throughout.
Likely lifecycle race
packages/ssh/src/tunnel.ts currently combines:
- a 1-second per-request readiness probe;
- a 2-second readiness window when deciding whether to reuse an existing tunnel;
- replacement when that short check fails;
- asynchronous child finalization and per-scope cleanup.
This makes a temporarily slow but valid tunnel look stale and allows repeated callers or connection scopes to replace it before the prior child has been fully cleaned up. An externally managed backend is also passed through the generic remote-stop finalizer path, even though the remote script currently avoids killing that server kind.
Expected behavior
- One authoritative tunnel generation per connection key.
- Concurrent ensure/reconnect requests converge on that generation.
- Replaced children are always reaped on replacement and manager shutdown.
- Readiness tolerates ordinary multi-second latency without treating one slow response as stale.
- Disconnecting an externally owned backend never requests remote shutdown.
- Repeated reconnect cycles keep both process and listener counts constant.
Proposed coverage
I have a small patch prepared that:
- raises the per-request readiness timeout to 5 seconds;
- gives reuse checks a 15-second bounded readiness window;
- explicitly skips remote stop for
serverKind: external; - adds a 20-way concurrent ensure test proving one child is created;
- adds a 100-cycle reconnect stress test proving the active tunnel count never exceeds one and every child is reaped.
The focused SSH package suite passes (27 tests), package typecheck passes, and vp check passes with only the repository's existing unrelated warnings.
No credentials, prompts, thread titles, repository content, hostnames, or Tailnet identifiers are included in this report.
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 in packages/ssh/src/tunnel.ts and run the focused SSH package suite to understand the existing readiness, reuse, replacement, and cleanup behavior. Done means concurrent ensures converge on one tunnel, reconnect cycles keep process and listener counts constant, replaced children are reaped, readiness tolerates ordinary latency, and externally owned backends are not remotely stopped.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 38/100