pingdotgg / pingdotgg/t3code

SSH reconnects can accumulate stale local-forward processes

Open
#4,144 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

🚧 In Progress bug
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.