Repository navigation
fix(ssh): reconnecting keeps the remote server and its agents running - #19432
Conversation
The managed server runs on the default T3 home, so it writes the default runtime file too. On every reconnect the launch script read that file, took the managed server for an external one, stopped it, and launched a new one, which ended every provider session running on the remote. When the default runtime file names the PID this launcher recorded as managed, treat it as our own server and take its port from that file, so a stale remembered port cannot hide it either. Without the managed marker the server is adopted as external, as before. Adopting an external server no longer falls back to stopping the PID the runtime file names, and a server that just answered adoption is not probed again while it is alive, so a momentary stall cannot launch a second server on the same home beside it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reconnect could kill the managed remote server and its running agents in two ways. A stale local forward ran the manager-owned remote stop when it was closed, and the launch script killed a live managed server that missed the 2-second reuse probe. Reconnect now closes a stale forward without the remote stop; explicit Disconnect/Remove keeps it. A live managed server gets the 60-second readiness budget, and if it still does not answer, the launch fails with a retryable error and leaves the server running. A changed runner still replaces the managed server. Squashed from #17919 and applied on top of #17684's launch script, whose same-PID guard replaces this PR's reordering of the reuse branches. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Drive the real launch script against a stopped fake server: a reconnect waits for a server that resumes after the old 2-second probes, and fails without stopping one that never answers. A changed runner still replaces the server. These replace #17919's shimmed-node cases with #17684's real-server harness. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kill -0 also succeeds for a zombie. Now that reconnect waits for a live managed server instead of replacing it, a crashed server left unreaped (for example under an init that does not reap orphans) would block every reconnect. Managed reuse now uses the same zombie check as adoption. Without ps to tell, both trust kill -0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a localized SSH reconnect bug fix that prevents stale tunnel cleanup from stopping the remote server or its agents, while preserving explicit disconnect and update replacement behavior. The added tests cover the affected lifecycle scenarios, including busy, adopted, and zombie processes. You can add or adjust custom eligibility rules. Learn more. |
Thread transfer impact✅ Thread transfer remains within every enforced ceiling.
Baseline: Scenario and decoded snapshot size10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.
Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Merge Risk: ⚪ Minimal · up to
|
|
Note 🤖 Claude Opus 5.5 responding on behalf of Theo The linked-issues warning is a deliberate scope limit, not a gap in the #16477 fix.
The case #16477 reports, a busy server killed by the 2 s probe, is fixed and covered by the busy-server and never-ready tests. |
## What's Changed * fix(clients): # suggestions include pull requests linked to the thread by @t3dotgg in pingdotgg/t3code#19357 * fix(clients): pairing a switched-off computer again turns it back on by @t3dotgg in pingdotgg/t3code#19354 * fix(cli): t3 triage reports the server's version, not its own, as the user's build by @t3dotgg in pingdotgg/t3code#19359 * fix(client-runtime): a new WSL backend no longer shows Limited permissions until reload by @t3dotgg in pingdotgg/t3code#19361 * fix(web): action shortcuts recorded in the browser now save by @t3dotgg in pingdotgg/t3code#19365 * fix(clients): large pasted images no longer break later Claude image reads by @t3dotgg in pingdotgg/t3code#19364 * fix(server): settled and idle-released threads no longer stay Waiting by @t3dotgg in pingdotgg/t3code#19355 * perf(client-runtime): merged pull requests are no longer re-read every minute by @t3dotgg in pingdotgg/t3code#19374 * fix(server): new Codex threads no longer miss slow MCP servers' tools by @t3dotgg in pingdotgg/t3code#19368 * fix(github): requested teams now show in pull request reviewers by @t3dotgg in pingdotgg/t3code#19375 * fix(ssh): SSH launch no longer picks a port another server answers on by @t3dotgg in pingdotgg/t3code#19372 * fix(server): a failed T3 Connect relink no longer leaves a stale managed tunnel by @t3dotgg in pingdotgg/t3code#19383 * fix(shared): Windows .cmd shims no longer print DEP0190 on every spawn by @t3dotgg in pingdotgg/t3code#19385 * fix(server): commit and title generation use your custom model when no text model is set by @t3dotgg in pingdotgg/t3code#19386 * fix(web): T3 Connect settings can repair a link that drifted to publish-only by @t3dotgg in pingdotgg/t3code#19389 * fix(server): failed Claude text generation shows the CLI's own error by @t3dotgg in pingdotgg/t3code#19394 * fix(web): terminal tells the shell its new width as soon as you release a resize drag by @t3dotgg in pingdotgg/t3code#19395 * fix(web): keybindings page says to connect an environment when none is connected by @t3dotgg in pingdotgg/t3code#19400 * fix(chat): Codex follow-up suggestions show their label instead of raw markup by @t3dotgg in pingdotgg/t3code#19401 * fix(server): open in editor works over Tailscale SSH without a local sshd by @t3dotgg in pingdotgg/t3code#19402 * fix(server): a deregistered T3 Connect host shows it was removed and offers a relink by @t3dotgg in pingdotgg/t3code#19379 * perf(server): idle servers no longer scan every process every 5 seconds by @t3dotgg in pingdotgg/t3code#19399 * chore(typecheck): bump tsc-rs to 0.7.0 by @t3dotgg in pingdotgg/t3code#19408 * fix(server): bound Bitbucket API requests with 30s timeout by @kvnloo in pingdotgg/t3code#14140 * fix(server): new worktrees no longer fail when a background fetch runs at the same time by @t3dotgg in pingdotgg/t3code#19411 * perf(server): thread search runs on the SQLite reader worker by @bompus in pingdotgg/t3code#19382 * perf(server): traces keep ~40% more history and no longer block the event loop by @t3dotgg in pingdotgg/t3code#19430 * fix(shared): SQLite checkpoints no longer fall back to the main thread silently by @t3dotgg in pingdotgg/t3code#19417 * perf(server): unchanged node updates no longer write to SQLite by @t3dotgg in pingdotgg/t3code#19429 * fix(ssh): reconnecting keeps the remote server and its agents running by @t3dotgg in pingdotgg/t3code#19432 * feat(observability): SQL spans show lock wait and connection; stalls keep their own history by @t3dotgg in pingdotgg/t3code#19425 * fix(server): threads woken by notifications no longer load their whole history by @t3dotgg in pingdotgg/t3code#19423 * fix(server): worktree cleanup no longer runs sweeps back to back by @t3dotgg in pingdotgg/t3code#19418 **Full Changelog**: pingdotgg/t3code@v0.0.46-nightly.20261014.3105...v0.0.46-nightly.20261014.3118 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.46-nightly.20261014.3118
## What's Changed * fix(clients): # suggestions include pull requests linked to the thread by @t3dotgg in pingdotgg/t3code#19357 * fix(clients): pairing a switched-off computer again turns it back on by @t3dotgg in pingdotgg/t3code#19354 * fix(cli): t3 triage reports the server's version, not its own, as the user's build by @t3dotgg in pingdotgg/t3code#19359 * fix(client-runtime): a new WSL backend no longer shows Limited permissions until reload by @t3dotgg in pingdotgg/t3code#19361 * fix(web): action shortcuts recorded in the browser now save by @t3dotgg in pingdotgg/t3code#19365 * fix(clients): large pasted images no longer break later Claude image reads by @t3dotgg in pingdotgg/t3code#19364 * fix(server): settled and idle-released threads no longer stay Waiting by @t3dotgg in pingdotgg/t3code#19355 * perf(client-runtime): merged pull requests are no longer re-read every minute by @t3dotgg in pingdotgg/t3code#19374 * fix(server): new Codex threads no longer miss slow MCP servers' tools by @t3dotgg in pingdotgg/t3code#19368 * fix(github): requested teams now show in pull request reviewers by @t3dotgg in pingdotgg/t3code#19375 * fix(ssh): SSH launch no longer picks a port another server answers on by @t3dotgg in pingdotgg/t3code#19372 * fix(server): a failed T3 Connect relink no longer leaves a stale managed tunnel by @t3dotgg in pingdotgg/t3code#19383 * fix(shared): Windows .cmd shims no longer print DEP0190 on every spawn by @t3dotgg in pingdotgg/t3code#19385 * fix(server): commit and title generation use your custom model when no text model is set by @t3dotgg in pingdotgg/t3code#19386 * fix(web): T3 Connect settings can repair a link that drifted to publish-only by @t3dotgg in pingdotgg/t3code#19389 * fix(server): failed Claude text generation shows the CLI's own error by @t3dotgg in pingdotgg/t3code#19394 * fix(web): terminal tells the shell its new width as soon as you release a resize drag by @t3dotgg in pingdotgg/t3code#19395 * fix(web): keybindings page says to connect an environment when none is connected by @t3dotgg in pingdotgg/t3code#19400 * fix(chat): Codex follow-up suggestions show their label instead of raw markup by @t3dotgg in pingdotgg/t3code#19401 * fix(server): open in editor works over Tailscale SSH without a local sshd by @t3dotgg in pingdotgg/t3code#19402 * fix(server): a deregistered T3 Connect host shows it was removed and offers a relink by @t3dotgg in pingdotgg/t3code#19379 * perf(server): idle servers no longer scan every process every 5 seconds by @t3dotgg in pingdotgg/t3code#19399 * chore(typecheck): bump tsc-rs to 0.7.0 by @t3dotgg in pingdotgg/t3code#19408 * fix(server): bound Bitbucket API requests with 30s timeout by @kvnloo in pingdotgg/t3code#14140 * fix(server): new worktrees no longer fail when a background fetch runs at the same time by @t3dotgg in pingdotgg/t3code#19411 * perf(server): thread search runs on the SQLite reader worker by @bompus in pingdotgg/t3code#19382 * perf(server): traces keep ~40% more history and no longer block the event loop by @t3dotgg in pingdotgg/t3code#19430 * fix(shared): SQLite checkpoints no longer fall back to the main thread silently by @t3dotgg in pingdotgg/t3code#19417 * perf(server): unchanged node updates no longer write to SQLite by @t3dotgg in pingdotgg/t3code#19429 * fix(ssh): reconnecting keeps the remote server and its agents running by @t3dotgg in pingdotgg/t3code#19432 * feat(observability): SQL spans show lock wait and connection; stalls keep their own history by @t3dotgg in pingdotgg/t3code#19425 * fix(server): threads woken by notifications no longer load their whole history by @t3dotgg in pingdotgg/t3code#19423 * fix(server): worktree cleanup no longer runs sweeps back to back by @t3dotgg in pingdotgg/t3code#19418 **Full Changelog**: pingdotgg/t3code@v0.0.46-nightly.20261014.3105...v0.0.46-nightly.20261014.3118 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.46-nightly.20261014.3118
Merges `pingdotgg/t3code` `e536bfd33b` (106 commits since `6002c5c68c`) into the fork. One merge commit; nothing stacked. ## Merge summary - **Landed:** 475 files against 476 in the upstream range. The one missing is `package.json`: its only upstream change was the `tsc-rs` 0.7.0 bump, which the fork drops (`root-package-json`). The fork delta is 809 files. - **Conflicts (5):** - `package.json`: dropped the `tsc-rs` bump. - `pnpm-lock.yaml`: stripped the `tsc-rs@0.7.0` entries from upstream's copy and re-derived it. - `Sidebar.tsx`: kept the fork's thread-list filter and took upstream's new `useSidebarProjectGroups` hook. That hook sorts project groups by all thread shells, not by the filtered list. - `SettingsPanels.tsx`: kept the `serverBehaviorSettings` gate and took upstream's new description. - `threadShell.ts`: kept the fork's subagent predicate and took upstream's `subagentRequests` argument. - **Fix after the merge:** upstream's new `useProjectScriptSettings.test.tsx` used `previewUrl`/`autoOpenPreview`. I changed it to the fork's `port`. - **Unsupported methods:** nothing to add or drop. `vcs.switchRef` and `git.preparePullRequestThread` stay as they are. - **Verification:** `verify.mjs` passes every check except `test`: - duplicate-adds, tripwires, resolution-check, lockfile, fmt, lint, typecheck (re-run after the fix) and build all pass. - Six test files fail on their own, all because of the sandbox: - the five recorded in earlier merges: `AgentScope` (`oom_score_adj`), `ServerBrowserPage` (no Playwright browser), `AcpAdapterV2`, `OrchestratorReplayFixtures`, `OpenCodeServerLedger`; - one new: `@t3tools/ssh` `tunnel.test.ts` from upstream pingdotgg#19432. It fails the same way `AcpAdapterV2` does: a killed process is never reaped, so it still reads as alive. The fork does not touch `packages/ssh`. - `@t3tools/web` tests pass after the fix. - Details are in `docs/fork/upstream-merge-log.md`. `docs/fork/gaps.md` has new notes under five existing entries: capabilities, settlement rules, pull request link, Orchestrator V2 (transcript read) and runtime fixes. ## Usable as-is - Waiting status icon and elapsed timer in the sidebar (pingdotgg#19617) - The settle shortcut moves on to the next thread, the same way the sidebar does (pingdotgg#19328) - Terminal resize, recorded-shortcut save, a keybindings page that prompts to connect, and settings that save after a stored value fails to decode (pingdotgg#19395, pingdotgg#19365, pingdotgg#19400, pingdotgg#19312) - The Mermaid code toggle stays in place, cursor stops stay outside styled markers, and live reasoning stays scrolled while it streams (pingdotgg#19601, pingdotgg#19651, pingdotgg#19334) - PR filters hide Scratch and the project filter follows sidebar order; `#` suggestions include the thread's PRs; merged PRs are no longer re-read every minute (pingdotgg#19179, pingdotgg#19180, pingdotgg#19357, pingdotgg#19374) - New provider-update-checks copy (pingdotgg#18208), shown only where the fork's `serverBehaviorSettings` gate already shows that row - Dependency bumps: Effect 4.0.4, OpenCode 2.0.28. `tsc-rs` 0.7.0 is deliberately dropped. ## Unsupported in Moatless / needs implementation - **Auto-settle keeps threads with a PR** (pingdotgg#14642): adds the `sidebarAutoSettleKeepPullRequests` setting and the `threadAutoSettlementScope` capability. Moatless does not report the capability, so the row stays hidden. - **Settled-worktree cleanup** (pingdotgg#15208): adds the `worktreeSettledAfterDays` setting and the `settledWorktreeCleanup` capability (Storage settings). Not reported, so the row is hidden. - **Thread transcript HTTP read** (pingdotgg#15210): `GET /api/orchestration/threads/:threadId/transcript`, used only for a thread dropped in from another environment. Moatless does not route it, and a single-environment deployment cannot reach it. - **`thread.pull-request-link-updated` event** (pingdotgg#19248): Moatless sends no pull-request frames; it changes the listing instead. - **Delegated-task question handling**: `hasPendingQuestion` on the orchestrator MCP task result (pingdotgg#18459, pingdotgg#19521, pingdotgg#19538). - T3 Connect revoked links, SSH and Tailscale, pairing, and desktop- and mobile-only items: not used by the Moatless web client. ## Backend behavior to consider reproducing in Moatless - Inactivity auto-settle that skips PR-linked threads (pingdotgg#14642); settled and idle-released threads stop reading as Waiting (pingdotgg#19355) - A retention sweep for settled threads' workspaces (pingdotgg#15208, pingdotgg#19418) - Store a PR link only when it changes, and stop re-reading merged PRs each turn (pingdotgg#19248, pingdotgg#19344) - GitHub reviewers include requested teams (pingdotgg#19375); new worktrees survive a concurrent fetch (pingdotgg#19411) - Commit and title generation fall back to the custom model (pingdotgg#19386) - Claude: `managed-mcp.json` is honoured and a 403 no longer says to log in (pingdotgg#18706, pingdotgg#15872) - Codex: "Always allow this session" works and slow MCP servers no longer drop tools (pingdotgg#19635, pingdotgg#19368) - CLIs behind mise wrappers no longer time out (pingdotgg#19340); cold usage scans stop stalling the server (pingdotgg#19446); a single database owner is enforced, with exit code 78 (pingdotgg#16102) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- Moatless task: https://moatless.soaplabstest.com/tasks/4979b4c3-e4cc-4911-849b-adb94cb65be6
A desktop SSH reconnect (laptop sleep, Wi-Fi drop, app restart) could stop the managed T3 server on the remote host and end every agent running there. It had three causes:
~/.t3/userdata/server-runtime.jsonitself. The launch script took that record for another server and stopped its own managed PID. This happened on every reconnect.Fix
server-runtime.jsonnames the PID this launcher recorded as managed, the script reuses that server and takes its port from the record. (fix(ssh): reconnecting no longer restarts the managed remote server #17684)#17919 also changed the reuse order and made adoption of another server wait 60 seconds with no launch fallback. That part is not included: #17684's PID check already stops the self-kill, and keeping main's adoption fallback means a stale runtime record cannot block reconnects. With #16102, that fallback cannot start a second server on the same home, because the new server exits with code 78.
Verification
vp test run packages/ssh/src/tunnel.test.ts, lint, and@t3tools/sshtypecheck pass. "archive runner script > installs once…" flakes locally on clean main too. That installer-lock code is unchanged here.Fixes #15986. Fixes #16477.
Combines #17684 by @puccez and #17919 by @luke2x. Their commits are kept with authorship. This supersedes both PRs and overlaps #13521.
Combined, tested, and reviewed by Claude Opus 5.5 in Claude Code (T3 Code). Reviewed with sol-loop: 2 rounds with GPT-6.1-Sol.
Co-authored-by: puccez 133161547+puccez@users.noreply.github.com
Co-authored-by: luke2x 97411838+luke2x@users.noreply.github.com
🤖 Generated with Claude Code