Skip to content

feat(cli): path resume takes --remote and plans a Claude resume on an ssh host - #244

Draft
ecalifornica wants to merge 2 commits into
ecalifornica/export-claude-derive-session-idfrom
ecalifornica/ssh-transport
Draft

feat(cli): path resume takes --remote and plans a Claude resume on an ssh host#244
ecalifornica wants to merge 2 commits into
ecalifornica/export-claude-derive-session-idfrom
ecalifornica/ssh-transport

Conversation

@ecalifornica

@ecalifornica ecalifornica commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

path resume takes --remote <user@host>, behind the resume-remote feature, and plans a Claude resume on that host. Next PRs in the stack add ship, launch, attach.

Three new modules:

  • ssh.rs: the transport, an in-process SSH client on the russh dependency.
  • cmd_resume/remote.rs: the resume. It reads the host state and decides between attach, launch, and ship.
  • claude_session.rs: the session ID derivation and remote project directory rules, which p export claude and path resume --remote both use with the addition of the remote resume.

toolpath-claude 0.13.3, path-cli 0.21.0, toolpath-cli 0.21.0. Stacked on #241.

@ecalifornica ecalifornica self-assigned this Aug 26, 2026
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown

🔍 Preview deployed: https://59cc246b.toolpath.pages.dev

@ecalifornica
ecalifornica force-pushed the ecalifornica/ssh-transport branch 2 times, most recently from c7fd8c9 to b17f788 Compare August 26, 2026 19:57
@ecalifornica ecalifornica changed the title feat(cli): path-cli gains an ssh transport module feat(cli): path resume takes --remote and resumes a Claude session on an ssh host Aug 26, 2026
Base automatically changed from ecalifornica/script-no-overwrite to main August 28, 2026 19:10
@ecalifornica
ecalifornica force-pushed the ecalifornica/ssh-transport branch from 884541b to 3265e87 Compare August 28, 2026 21:11
@ecalifornica
ecalifornica changed the base branch from main to ecalifornica/export-claude-derive-session-id August 28, 2026 21:11
@ecalifornica
ecalifornica force-pushed the ecalifornica/ssh-transport branch from 3265e87 to 198f3e5 Compare August 28, 2026 22:59
@ecalifornica
ecalifornica force-pushed the ecalifornica/ssh-transport branch from 198f3e5 to e664212 Compare August 28, 2026 23:40
@ecalifornica ecalifornica changed the title feat(cli): path resume takes --remote and resumes a Claude session on an ssh host feat(cli): path resume takes --remote and plans a Claude resume on an ssh host Aug 28, 2026
`ssh.rs` runs commands on a remote host through an in-process SSH
client (`russh`). The destination is `user@host`; the port is 22. The
caller passes the agent socket and the ssh directory; the module
reads no environment variable. The agent authenticates first, then
the default identity files in the ssh directory; an encrypted file
is skipped with a hint. `known_hosts` in that directory verifies the
host key: a changed key is an error, and an unknown host is learned
on first contact with a notice (accept-new).

`Transport::run` opens one exec channel per call: one task feeds
stdin while another collects stdout, stderr, and the exit status, all
under the caller's wall-clock timeout; on expiry the connection drops
and the error carries the stderr received so far. Connect, handshake,
and authentication share a 30s bound. Keepalives are on every
session.

`RemoteCommand` is the only way to build the string the exec request
carries. `new` is an argv. `script` is a constant `sh` script that
reads its values as positional parameters; the text is `&'static
str`, so a value cannot be interpolated into shell text. Every word
is quoted through shlex. `Destination` requires `user@host`; each side
starts alphanumeric and continues with `[A-Za-z0-9._-]`.

`parse_facts` reads `TP_<NAME>=<value>` lines and rejects any other
shape, so a login banner cannot become a path component. `FakeSsh`
scripts replies and records rendered commands for tests. Quoting and
scripts are tested against a real `sh`; the live test (`--ignored`,
`PATH_TEST_SSH_DEST`) exercises run, stdin, stderr, the exit status,
and the timeout against a real host.

The module and its dependencies `russh` and `shlex` compile only with
the `resume-remote` cargo feature. The module has no caller in this
commit, so `lib.rs` allows dead code on it. No version bump: the
dependencies become user-visible with the command that consumes the
module.
… ssh host

`path resume <input> --remote <user@host> [-C <dir>] [--dry-run]`
plans a resume on an ssh host without touching it. Claude only. Two
read-only ssh calls through the transport module gather the facts:

- Call 1: the remote home, the claude path (PATH, then ~/.local/bin,
  ~/.claude/local, ~/.npm-global/bin), tmux presence. The remote
  project directory is `-C`, else the local cwd with the local home
  swapped for the remote home.
- Call 2: the directory is physical (`pwd -P` returns it), the tmux
  session for the derived ID is live, the target session file exists.

The plan prints the facts and what a run does: a live tmux session is
attached to, a present session file is launched, an absent file is
shipped. `--dry-run` exits after the plan. Without it the command
errors: ship, launch, and attach are not implemented yet.

The session ID comes from `session_id_from_document_hash` and `-C`
follows `parse_cwd_arg`, both shared with `p export claude`; they
move from `cmd_export` to the new `claude_session` module, the owner
both commands import from. `resolve_input` returns a `ResolvedInput`
carrying the document text next to the parsed graph, so the ID hashes
the input bytes rather than a type round-trip. The tmux session name
is `path-<first 8 of the ID>`.

The probe scripts are `probe_host.sh` and `probe_project_dir.sh` next
to the module, included with `include_str!`. They print
`TP_<NAME>=<value>` lines and the parser rejects any other output, so
a login banner cannot become a path component. A captured path must
be a non-empty single line starting with `/`. A non-physical
directory errors with the physical path as a `-C` hint. Remote paths
are joined with `/`: `remote_slug_dir` takes only the slug component
from `PathResolver`, and the `-C` default joins the cwd's components
with `/`, so a Windows local host produces the same remote paths as a
Unix one. Tests drive the command through the scripted fake
transport; nothing is public for tests.

The flags and the planner compile only with the `resume-remote` cargo
feature, and `ResumeArgs` derives `Default`. `run_with_strategy`
reads `SSH_AUTH_SOCK` and the home directory and hands them to the
transport.

toolpath-claude 0.13.3 (sanitize_project_path is public); path-cli
0.21.0; toolpath-cli 0.21.0 (lockstep bump of the shim).
@ecalifornica
ecalifornica force-pushed the ecalifornica/ssh-transport branch from e664212 to 4cb7400 Compare August 29, 2026 00:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant