Skip to main content
← Back to list
01Issue
BugIn ProgressExtensions
Assigneesstack72

Relationships

#845 copy/rsync ignores transport extraOptions (and proxyCommand), unlike exec/script

Opened by atalanta · 6/26/2026

Problem

When a host's ssh transport sets extraOptions (or proxyCommand), those options are applied to exec and script but silently dropped for copy when useRsync: true. This makes it impossible to express, for a copy/rsync, anything that isn't identityFile/proxyJump/port — most importantly a custom ProxyCommand.

Why this bites

OpenSSH's -J <user@host> does not propagate the command-line -i to the jump hop — each hop resolves its own identity from ssh_config. With no ssh_config entry for the jump host, the jump leg offers the user's default keys and is denied. The documented-in-the-wild workaround is to supply an explicit ProxyCommand that carries -i on the jump leg, via the transport's extraOptions. This works for exec/script but not for rsync copies, so a fleet that can run commands through a jump host cannot deploy files through the same jump host.

Evidence (models/_lib/runner.ts @ 2026.06.04.2)

sshCommonOpts() (used by exec/script) appends both proxyCommand and extraOptions:

if (t.proxyCommand !== undefined) o.push('-o', `ProxyCommand=${t.proxyCommand}`);
for (const extra of t.extraOptions) o.push('-o', extra);
...
if (t.proxyJump !== undefined) o.push('-J', t.proxyJump);
if (t.identityFile !== undefined) o.push('-i', t.identityFile);

But buildRsyncArgv() hand-rolls its own -e ssh ... string and includes only ControlPath, identityFile, proxyJump, and port — no proxyCommand, no extraOptions:

if (ctx.controlPath && t.controlMaster.enabled) sshParts.push('-o', `ControlPath=${ctx.controlPath}`);
if (t.identityFile) sshParts.push('-i', t.identityFile);
if (t.proxyJump) sshParts.push('-J', t.proxyJump);
sshParts.push('-p', String(t.port));

(buildScpArgv likely has the same gap — worth checking.)

Repro

  1. Fleet with a host that reaches its target via a jump host, configured with extraOptions: ['ProxyCommand=ssh -i <key> -W %h:%p user@jump'].
  2. exec on that host succeeds.
  3. copy --useRsync true to the same host fails — the emitted -e ssh ... omits the ProxyCommand, so rsync connects directly (here: times out).

Proposed fix

Have buildRsyncArgv (and buildScpArgv) reuse the same option assembly as sshCommonOpts — or at minimum also append -o ProxyCommand=... and the extraOptions entries — so copy/rsync/scp honor the full transport like exec/script do. The -e string just needs the same -o flags.

Environment

  • @swamp/ssh 2026.06.04.2
  • macOS, OpenSSH client; OpenBSD 7.6 target reached via a Fedora jump host (key auth both legs).

Upstream repository: https://github.com/swamp-club/swamp-extensions

Environment

  • Extension: @swamp/ssh@2026.06.04.2
  • swamp: 20260626.005336.0-sha.f302d18e
  • OS: darwin (aarch64)
  • Deno: 2.8.3
  • Shell: /bin/zsh
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 2 MOREREVIEW+ 1 MOREIMPLEMENTATION

In Progress

6/27/2026, 12:52:35 AM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack726/27/2026, 12:46:30 AM

Sign in to post a ripple.