Skip to content

fix(ssh): use the remote service home for launch and pairing - #24100

Open
nateEc wants to merge 6 commits into
pingdotgg:mainfrom
nateEc:codex/fix-17039-ssh-custom-home
Open

nateEc wants to merge 6 commits into
pingdotgg:mainfrom
nateEc:codex/fix-17039-ssh-custom-home

Conversation

@nateEc

@nateEc nateEc commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Problem

SSH discovery, server launch, and pairing always use ~/.t3, even when the remote machine's existing T3 service uses a custom home. Adding an SSH route can therefore start a second environment and issue credentials from the wrong store.

Fixes #17039. The failure and intended resolution order were established in maintainer triage.

Fix

Resolve the remote home using explicit T3CODE_HOME, then the installed systemd unit or launchd plist, then the default. Reuse the existing service-file parser and home expansion rather than evaluating shell configuration. Discovery and serve --base-dir use that result; save it for pairing so a later non-login shell cannot silently choose another home.

Keep launcher ownership and runtime cache paths where they are; this does not move existing data or installations. If a legacy managed server is still running in a different home, fail with an actionable message and leave both its process and runner intact. Do not reuse an old home's remembered external port when the selected service is unavailable. External services remain external and are not stopped.

This changes the SSH backend only; no client layout, provider behavior, authentication scope, or wire contract changes. Existing unrelated SSH alias ownership and old-server/new-runner compatibility issues are outside this fix.

Verification

  • Before the fix, a real /bin/sh pairing-script test with isolated home and a fixture CLI failed: --base-dir was <fixture>/.t3 instead of the explicitly configured custom home.
  • vp test run packages/ssh/src/tunnel.test.ts apps/server/src/cli/sshHelper.test.ts apps/server/src/cloud/bootService.test.ts: 44 passed on macOS arm64, Node 26.12.1.
  • The shell tests run the production-generated scripts locally instead of SSH. A fixture runner checks custom-home adoption, persisted pairing-home selection, refusal to reuse a responsive old-home port, and preservation of an owned live fixture process and its runner when a home change is refused. No real server or credentials are used.
  • Resolver tests cover systemd and launchd formats, quoted/special-character paths, explicit environment precedence, tilde expansion, and default fallback. POSIX-shell tests are skipped on Windows; resolver tests are platform-independent.
  • Actual CLI invocation: T3CODE_HOME=/tmp/t3-17039-cli-path-proof node apps/server/src/bin.ts __ssh-helper home-dir printed exactly that path.
  • vp run check:changed: passed formatting, lint, knip, server GraphQL validation, scoped typechecks, and both directly affected test files (11 tests).

Not verified against a real remote SSH host or a packaged desktop build. No production state was modified and no UI evidence is claimed.

Model: GPT-5-based Codex. Harness: Codex desktop app.

- 按环境变量、已安装服务配置和默认值解析远端 T3 目录,并让配对复用启动时的目录。
- 保留既有启动器状态和缓存路径;切换目录时保护旧托管进程,避免复用另一环境的端口。
- 补充真实 shell 脚本与服务配置回归测试;44 项针对性测试及 check:changed 通过。
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Oct 23, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — SSH launch and pairing now automatically use the remote configured service home instead of always ~/.t3. A human must confirm this default environment-selection change, which determines the server and credential store used.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 9f7f09f and 7ec6e48.

Files selected for processing: 4
  • apps/server/src/cli/sshHelper.ts
  • apps/server/src/cloud/bootService.test.ts
  • packages/ssh/src/tunnel.test.ts
  • packages/ssh/src/tunnel.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

Walkthrough

The SSH helper resolves the server home from T3CODE_HOME or the installed service configuration. The SSH launcher uses that home for server discovery, launch, and pairing, and updates its handling of changed homes and process state.

Changes: SSH server home and pairing

Layer / File(s) Summary
Resolve the SSH server home
apps/server/src/cli/sshHelper.ts, apps/server/src/cloud/bootService.test.ts
The helper resolves a nonblank T3CODE_HOME or the home from the platform service file. The home-dir subcommand prints the resolved path. Tests cover service-home fallback and T3CODE_HOME precedence.
Use the resolved home during launch
packages/ssh/src/tunnel.ts, packages/ssh/src/tunnel.test.ts
The launcher derives the runtime file from the resolved home and records that home in state. It clears remembered server state when the home changes, rejects a live previously managed server at a different home, and handles zombie processes and readiness failures. Tests cover service reuse and managed launch behavior.
Use the recorded home for pairing
packages/ssh/src/tunnel.ts, packages/ssh/src/tunnel.test.ts
Pairing uses the stored server home, or resolves it through the runner when none is stored. Tests cover a configured home and a home recorded in SSH state.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant SSHLauncher
  participant InstalledRunner
  participant HomeResolver
  participant ServiceConfiguration
  participant RuntimeFile
  SSHLauncher->>InstalledRunner: invoke home-dir
  InstalledRunner->>HomeResolver: resolve server home
  opt T3CODE_HOME is blank
    HomeResolver->>ServiceConfiguration: read configured home
    ServiceConfiguration-->>HomeResolver: return configured home
  end
  HomeResolver-->>InstalledRunner: return resolved home
  InstalledRunner-->>SSHLauncher: print resolved home
  SSHLauncher->>RuntimeFile: inspect runtime state under resolved home
Loading

Possibly related issues

  • pingdotgg/t3code#23858 — The PR resolves the SSH home from installed service configuration, which overlaps the issue’s custom-base-directory objective.

Suggested reviewers: t3dotgg

Merge Risk: Minimal · up to 7ec6e

No concrete issue remains that needs resolution before merging; proceed with normal checks.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main SSH change: using the remote service home for launch and pairing.
Description check Passed The description is complete and relevant. It explains the problem, the resolution, scope, linked issue and maintainer triage, verification results, test limitations, and tool usage. It uses “Fix” inst…
Linked Issues check Passed Issue [#17039] is directly linked and active. resolveSshServerHome uses nonblank T3CODE_HOME, then the installed systemd unit or launchd plist, then the default home. SSH discovery and `serve --ba…
Out of Scope Changes check Passed The changes remain within issue [#17039]. sshHelper.ts provides remote-home resolution. tunnel.ts applies that result to discovery, launch, and pairing, and adds the required home-change and port …
Full details: Description check

Explanation

The description is complete and relevant. It explains the problem, the resolution, scope, linked issue and maintainer triage, verification results, test limitations, and tool usage. It uses “Fix” instead of the template’s “Change” heading and does not include a separate “Scope and approval” heading, but it provides the required information in other sections.

Full details: Linked Issues check

Explanation

Issue [#17039] is directly linked and active. resolveSshServerHome uses nonblank T3CODE_HOME, then the installed systemd unit or launchd plist, then the default home. SSH discovery and serve --base-dir use the selected home. Launch persists server-home, and pairing uses that value. The launch logic rejects a live legacy managed server in another home without stopping its process or runner. It does not reuse an old-home port when the selected service is unavailable. Tests cover home resolution, pairing persistence, service adoption, port handling, and the refusal path. The issue lists moving launcher state and runtime cache as one possible direction, but the PR explicitly keeps those paths unchanged and does not claim to migrate existing data.

Full details: Out of Scope Changes check

Explanation

The changes remain within issue [#17039]. sshHelper.ts provides remote-home resolution. tunnel.ts applies that result to discovery, launch, and pairing, and adds the required home-change and port safeguards. The test changes directly verify these behaviors and the service-file formats. No changes target client layout, provider behavior, authentication scope, wire contracts, or unrelated SSH features.

Finishing Touches
Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:M 30-99 changed lines (additions + deletions). labels Oct 26, 2026

maria-rcks commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

Note

Written by gpt-6.1-sol on behalf of Maria

updated to 7ec6e48c92665437ec560550bf0ce21d41504fab by merging current main without rebasing. resolved the ssh test import conflict while keeping both imports. pushed to the original contributor branch without force.

addressed two scoped recovery findings: ignore dead or zombie runtime pids before custom-home adoption, and record the selected home before marking a newly launched server managed and waiting for readiness. extended the existing shell test; it failed on the old readiness path and then on the missing home metadata before both fixes passed. no new test files. the added recovery fix is five production lines.

blacksmith vp run check:changed github/main passed format, lint, both knip checks, graphql validation, five package typechecks, and all 50 tests. the real source cli also resolved a service home containing spaces and honored explicit environment-home precedence. two independent reviews approve this exact head with no remaining findings. no client rendering changes.

this pass did not repeat the prior full openssh/client/provider exercise or verify macos, windows, packaged desktop, or the release archive. prior runtime evidence follows and remains tied to its original head.

previous verification

updated the original branch, preserving nateEc's commits and merging main. kept the home-resolution and persisted-pairing behavior. moved resolver coverage into the existing service test file, removing the new test file. reused the launcher's existing zombie-aware process check so an exited managed server cannot block a home change.

verification at ca82cea91605322cb5fbe48570bc88cc0e0a13f2:

  • blacksmith vp run check:changed passed formatting, lint, both knip checks, graphql validation, typechecks for four affected packages, and all 44 adjacent tests. both original home regressions failed against main; the added zombie recovery assertion failed before the guard correction.

  • real openssh to a disposable linux account, using the actual source cli and a running systemd user service configured with a home containing spaces:

    main: launch -> ~/.t3, port 3774, managed duplicate
          pairing token exchange against custom-home service -> 401
    final: launch -> existing custom-home service, port 41773, external
           pairing token exchange against that service -> 200
           service pid unchanged: 37533
    final: service stopped -> managed launch uses custom home
           pairing token exchange -> 200
    final: service file changed after launch
           pairing still uses saved launch home -> 200
    
  • after stopping the service, ssh also launched a managed server in the custom home; its pairing exchange returned 200.

  • github ci passed on the final head, including all eight server shards and build/typecheck.

  • two independent final-head reviews approve, with the zombie finding fixed and no remaining blockers.

unverified: macos/launchd runtime, windows ssh, and packaged desktop end-to-end. launchd resolution is covered by the existing-file tests. no client rendering changes. human approval of the home-selection and credential-store behavior is still required.

maria-rcks commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified head ca82cea91605322cb5fbe48570bc88cc0e0a13f2 on a disposable linux boat vm. no source changes were needed.

  • reproduced on github/main (9f33a1c836), installed in a separate worktree: the production ssh pairing function invoked the real cli over openssh and created a credential in the default home. the running custom-home server rejected it with http 401. the pr's credential was accepted with http 200.
  • exercised service-file home discovery with a path containing spaces, explicit T3CODE_HOME precedence, and default fallback. after changing the service file, pairing still used the home recorded during launch and remained accepted.
  • the production ssh environment manager adopted the existing server as external, created a real ssh forward, paired the browser, and reused that forward on reconnect. disconnect closed the forward and left the external server's pid and http endpoint intact.
  • created a project through the web ui pointing at a real git repository. two real codex gpt-6.1-sol turns ran commands and completed, including a continuation whose http and websocket traffic passed through the ssh forward. no mocks, replay provider, or seeded data.
  • actual managed launch and pairing worked in both the default home and the custom home. the custom-home launch wrote its runtime file there and accepted its pairing credential with http 200. changing the service home while that managed server was live produced the expected refusal, preserving its pid and responsive endpoint. explicit disconnect then stopped it.
  • blacksmith ran the existing ssh and service suites: 44 passed. the web build passed. the inspected head reports 31 non-failing ci contexts (21 successful, 9 skipped, 1 neutral), zero pending. task servers and forwards were stopped.

real codex result through the ssh forward

command transcript and environment details

Not verified

packaged electron ui, macos launchd, windows, native ios/android, a separate physical ssh host, and the release-archive runner. ssh used a real loopback openssh server and the source cli.

two environment limitations were encountered: the standard dev backend redirects / to vite with http 302, which the existing ssh readiness probe rejects; and the blacksmith-built web bundle failed to start at icons-Dqr5UY3I.js:108:305 with Cannot read properties of undefined (reading 'map'). discovery was verified against the source cli serving built assets; client/provider integration used the normal vite client with its api/websocket proxy pointed through the real ssh forward. built-client startup remains unverified. neither issue was changed as part of this ssh verification.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SSH launcher ignores a custom T3CODE_HOME and starts a second environment instead of reusing the service

2 participants