fix(agent): install agent using an absolute remote path - #10
Open
aqandrew wants to merge 1 commit into
Open
Conversation
Mutagen copies the agent binary with scp and then executes it over ssh using the same "~/"-prefixed path. This relies on "~" resolving to the same location for both steps, which only holds when the remote SSH/SFTP working directory is the user's home directory. Coder workspaces can configure a different working directory (via coder_agent.dir, or a devcontainer whose workspaceFolder differs from $HOME). In that case scp resolves the path relative to the working directory while the ssh exec expands "~" to $HOME, so the freshly-copied agent binary can't be found and installation fails with e.g.: no such file or directory: /home/ubuntu/.mutagen-agent<UUID> Resolve the absolute remote home directory once and use it for both the copy and the invocation so they agree regardless of the remote working directory. If resolution fails, fall back to the previous behavior. Fixes the file sync failure tracked in coder/coder-desktop-macos#238 and Linear DEVEX-522. Signed-off-by: Andrew Aquino <dawneraq@gmail.com>
aqandrew
force-pushed
the
andrew/devex-522-agent-absolute-path
branch
from
September 1, 2026 18:36
cc1eb98 to
20bde82
Compare
aqandrew
marked this pull request as ready for review
September 2, 2026 18:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fixes DEVEX-522
Summary
Mutagen copies the agent binary to the remote with
scpand then executes it overssh, both using a~/-prefixed path (~/.mutagen-agent<UUID>). This assumes~resolves to the same place for both steps, which only holds when the remote SSH/SFTP working directory is the user's$HOME.Coder workspaces can configure a different working directory:
coder_agent.dirset to a non-home path (File sync fails when coder_agent.dir is set to a non-home directory coder-desktop-macos#238), orworkspaceFolderdiffers from$HOME(Linear DEVEX-522), e.g.workspaceFolder=/home/ubuntu/figma/figmawhile$HOME=/home/ubuntu.In those cases
scpwrites the binary relative to the working directory while thesshexec expands~to$HOME, so installation fails:Change
In
pkg/agent/install.go, resolve the absolute remote home directory once (viaecho "$HOME"over the transport) and use it to build an absolute path for both thescpcopy and thesshinvocation, so they always agree regardless of the remote working directory. If home resolution fails or returns a non-absolute value, it falls back to the previous~/-relative behavior. Scoped to POSIX remotes;cmd.exebehavior is unchanged (it already uses a relative name).Why absolute rather than a relative path: absolute is the only form that is correct across all Coder agent versions (before and after the SFTP working-directory change in coder/coder#21194), because it bypasses cwd/tilde conventions entirely.
Testing
go build ./pkg/agent/andgo vet ./pkg/agent/pass;gofmtclean.TestRemoteHomeDirectorycovers trimming, empty/non-absolute/unexpanded output, and command failure. Passes.dir != $HOME):scp host:~/xandscp host:xboth land in the working dir (scp strips~/),ssh '~/x'resolves to$HOME, and absolute paths resolve identically for both — confirming this fix aligns the two steps.TestExecutableForPlatform*failures are unrelated (they require a built agent bundle in./build).Rollout (follow-ups, not in this PR)
coder/mutagenrelease and publish artifacts, then bumpResources/.mutagenversionin the Coder Desktop clients (macOS and Windows).Context
Chosen approach per Linear discussion (fix in the Mutagen fork; "most compatible behaviour" and the fork is already maintained). Alternatives considered — changing
coder sshto land in$HOME, or a devcontainer cwd toggle — were rejected to preserve the "SSH lands in the workspace folder" behavior. Acoder/coderSFTP~-expansion fix is not viable becausescpstrips~/before the server sees it.🤖 Opened by Coder Agents on behalf of @aqandrew. Draft for review.
Implementation plan / decision log
DEVEX-522 — File Sync fails when the remote working directory differs from
$HOMETracking:
coder/coder-desktop-macos#238(thecoder_agent.dirvariant, closed viadeprecation) and DEVEX-522 (the devcontainer
workspaceFoldervariant, still open).1. Problem statement
Coder Desktop File Sync (Mutagen) fails to bootstrap its agent binary whenever the
remote SSH session's working directory is not the user's
$HOME. The user sees:Two triggers, same root cause:
coder_agent.dirset to a non-home directory (issue Mutagen compose withoutmutagen composemutagen-io/mutagen#238).workspaceFolderdiffers from$HOME(DEVEX-522), e.g.workspaceFolder = /home/ubuntu/figma/figmawhile$HOME = /home/ubuntu.This reproduces even with
coder_agent.dirunset, which is why Mutagen compose withoutmutagen composemutagen-io/mutagen#238's"unset
dir" workaround and the provider deprecation do not resolve it.2. Root cause (confirmed in code)
Mutagen bootstraps its agent in two steps that resolve paths through different
mechanisms:
coder/mutagenpkg/agent/install.go:transport.Copy(pkg/agent/transport/ssh/transport.go) runsscpwithdestination
host:~/.mutagen-agent<UUID>. The Coder agent's SFTP server(
agent/agentssh/agentssh.go→sftpHandler→sftp.WithServerWorkingDirectory(dir))sets the SFTP working directory to the resolved session directory
(
coder_agent.diror, for a devcontainer sub-agent,workspaceFolder). Thebinary therefore lands under that directory, not
$HOME.pkg/agent/install.goinstallCommand := fullRemotePath + " install"runs
~/.mutagen-agent<UUID> installover SSH. The remote login shell expands~to$HOME, so it looks in$HOMEand fails with the absolute-path errorabove.
The Coder agent SFTP working-directory change landed in
coder/coder#21194(SFTP/SCP now respect
coder_agent.dir); the original scp-vs-ssh CWD mismatch iscoder/coder#16568.Empirically verified path resolution (live probe against a real agent)
Tested against a Coder workspace whose agent has
dir = /home/coder/coderand$HOME = /home/coder(scp/sftp/ssh via the Coder net):scp host:X(relative)dirscp host:~/Xdir(no literal~dir created)~/client-side → relative → SFTP working dirscp host:/abs/X/absssh host '~/X'$HOME~ssh host 'X'(relative)dirDecisive consequences:
scpupload dir andsshcommand cwd are already identical (dir). Themismatch survives only because Mutagen uses
~/, which scp strips todirbut the shell expands to
$HOME.scpremoves the leading~/before sending, so the agent SFTP server neverreceives a tilde. A server-side
~-expansion fix therefore cannot interceptit (confirmed: the interactive
sftpclient'scd ~resolves todir/~and fails — the server treats~as a literal component and never maps itto home). This rules out a
coder/coderSFTP fix for the scp/Mutagen path.cwd/tilde conventions.
Note: the connect/invocation path (
pkg/agent/dial.goagentInvocationPath→~/.mutagen/agents/<version>/mutagen-agent) is not affected, because theagent's own
Install()relocates the binary using the remote's real home(
filesystem.Mutagen(...)) and the invocation runs over SSH where~alsoresolves to
$HOME. Both sides of that path agree. The only cross-transport(scp + ssh) inconsistency is the temporary
.mutagen-agent<UUID>upload path ininstall.go. That is the single bug locus.Devcontainer routing (confirmed in
coder/coder)FileSyncSessionModal.swift), includingdevcontainer sub-agents, and the user picks one; Mutagen SSHes to that host.
agentprocess running its ownagentssh.Serverinside the container (agent/agentcontainers/api.gorunSubAgentInContainer→cli/agent.go→agent.New/agent.go:415SSH server).Its
WorkingDirectorycomes from its own manifestDirectory, set to thecontainer-internal
workspaceFolderfromdevcontainer read-configuration(
api.gosubAgentConfig.Directory = workspaceFolder).workspaceFolder(viaresolveWorkingDirectory→WithServerWorkingDirectory).ExperimentalContainers && container != ""SFTP guard does not fire for adirect connection to the sub-agent:
containeris only populated when the SSHclient sends
CODER_CONTAINER(that's thecoder ssh --containerparent path).The sub-agent's own env sets only
CODER_AGENT_URL/TOKEN, socontainer == ""and SFTP is allowed.
BlockFileTransferdefaults to false.selecting the sub-agent routes straight to it.
Conclusion: in the Figma scenario (
dirunset, devcontainerworkspaceFolder = /home/ubuntu/figma/figma), the failing transfer is served by thedevcontainer sub-agent (SFTP allowed, cwd =
workspaceFolder). Syncing to theparent agent (cwd falls back to
$HOME) would not hit this bug.remoteUser/containerUsercan further shift$HOMEand are worth capturing in repro.3. Is Blink's analysis good? (answer to the third attachment)
Directionally correct and operationally useful, with two real flaws.
Right:
mutagen composemutagen-io/mutagen#238 —workspaceFoldervs$HOMEmismatch. Correct; laterconfirmed by Beebs's
ls(binaries under/home/ubuntu/figma/figma, error looksin
/home/ubuntu).$HOMEvs
pwd, inspectremoteUser/containerUser, and test parent-agent vssub-agent targets. The user/home angle is a legitimate extra devcontainer variable.
workspaceFoldervariant.Wrong / weak:
point 1.
/home/ubuntu/.mutagen-agent...is exactly the expected output: theexec command is
~/.mutagen-agent<UUID> installand the remote shell expands~to
$HOME. It is not evidence of a different filesystem/user namespace or aresolution anomaly. The two speculative theories overcomplicate a well-understood
mismatch.
path under
$HOME— will not fix it. The failure is in Mutagen's agent-binarybootstrap path (
~/.mutagen-agent<UUID>), which is independent of the syncsession's beta/remote path. Changing the sync target does not relocate the agent
upload/exec.
Mutagen fork) or the container-SFTP-blocked constraint. Acceptable for a support
reply, but not a solution.
Blink's earlier (first) message adds nothing to the root cause but reinforces the
pattern:
coder_agent.dirnon-home + #21194) was identified correctly from thestart, but hedged with "(or vice versa)" — the scp-vs-ssh direction was never
pinned. Evidence pins it: scp lands in
dir/workspaceFolder, exec resolves~→$HOME.The exec command is
~/.mutagen-agent<UUID> install; zsh expands~to$HOMEregardless of any
cdin~/.zshrc/~/.zprofile. Thezsh:1:prefix onlyidentifies the login shell. This is another speculative wrong tangent on top of a
correct core diagnosis.
dirso it defaults to$HOME") is correct for theMutagen compose without
mutagen composemutagen-io/mutagen#238 variant (unlike the message-3 remote-path workaround), but still does not fixthe DEVEX-522 devcontainer variant, where
workspaceFolderreintroduces themismatch with
dirunset.Verdict: solid triage and a correct core diagnosis; discount the speculative theories
(zsh-startup, "suspicious path", filesystem/namespace) and the message-3 remote-path
workaround.
4. Proposed fix
4.0 Key finding: no server-side fix that preserves the workspace-folder cwd
The live probe (Section 2) plus the Linear discussion settle the fix location:
scpstrips the leading~/client-side, so the agent never receives a tildeto expand. A server-side
~-expansion option inpkg/sftpwould be dead code forthe scp/Mutagen path (nobody proposed it, correctly).
scp coder:~/foo(user means home) fromscp coder:foo(user means cwd) — both arrive as the same relative path.coder/coderoptions that would work change or gate the devcontainercwd itself (Asher's options 1 and 2 — see 4d), sacrificing or opt-outing the
"SSH lands in the workspace folder" behaviour. The team rejected those in favour of
keeping that behaviour.
Decision (Linear, Atif): fix it in the
coder/mutagenfork — "most compatiblebehaviour" and "we already maintain a fork." This reverses the earlier
"no fork patch" stance specifically for the devcontainer case, where deprecation
cannot help.
4a. Primary change —
coder/mutagenfork: absolute agent pathIn
pkg/agent/install.go(install), resolve the remote home directory once and usean absolute path for both the scp
transport.Copyand the... installexec, sothey agree regardless of cwd/tilde conventions.
probe(...), resolve$HOMEover the SSH transport (POSIX:printf %s "$HOME"; cmd.exe:echo %USERPROFILE%). Validate non-empty + absolute; on failurefall back to today's
~-relative behavior.fullRemotePath := join(remoteHome, remoteFileName)(absolute) and use it fortransport.Copy, thechmod +xcommand, and the install command.remotePathFromHome/agentInvocationPath(dial.go) unchanged — that pathis already consistent.
Why absolute (not "drop the
~/and go relative"): absolute is the only variantcorrect across all server versions. The probe shows why:
~/X(current)X(relative)/home/user/X(absolute)This is exactly the approach Atif described considering. His objection
("only fixes Coder Desktop; plain scp/vanilla Mutagen still misbehave when
dirisset") remains factually true — the probe confirms
scp coder:~/foostill lands indir. But that objection was raised when deprecatingdirwas the chosenalternative; for devcontainers there is no such alternative, and this change only
affects Mutagen's internal agent bootstrap (it does not alter user-facing
scpsemantics). Requires team sign-off since it reverses the prior decision — see 4d.
4b. Tests —
coder/mutagenincluding the empty/
$HOME-unset fallback.pkg/agent/install_test.goso a non-home working directory yields matchingupload + exec paths.
4c. Ship to Coder Desktop
coder/mutagenrelease (scripts/ci/build.shbuildsmutagen-agents.tar.gzmutagen-darwin-{arm64,amd64}), publish togs://coder-desktop/mutagen/<ver>/.Coder-Desktop/Coder-Desktop/Resources/.mutagenversion(currentlyv0.18.3)in
coder-desktop-macos;make clean/mutagen && make.4d. Decision (made in Linear) and the alternatives it beat
Atif selected option 3 — modify the Mutagen fork (this plan's 4a). Rationale:
most compatible behaviour; the fork is already maintained. Asher's other two options
were considered and rejected:
coder sshto land in$HOME, not the workspace folder. Would fix themismatch (realigns scp-relative and ssh-
~) but is enforced for all usersimmediately — jarring, and drops the devcontainer "land in the repo" UX.
devcontainers-ssh-into-workspace-folder,default true) to opt out of workspace-folder cwd. Works, but adds surface area and
still doesn't fix the default configuration.
cost is that other SFTP tools (
scp coder:~/foo, vanilla Mutagen) remaininconsistent when
dir/workspaceFolderis set — acceptable, and worth a docsnote (4c).
Implementation note: the fork change is the absolute-path bootstrap in 4a. Confirm
with Asher/Atif whether they want absolute-path resolution (robust across server
versions) versus any narrower variant before opening the PR.
4e. Non-goals
coder/coderSFTP~-expansion: ineffective (scp strips the tilde). Dropped.#21194(make SFTP cwd$HOMEwhile ssh staysdir): reintroduces#16568.coder_agent.dir(already shipped for Mutagen compose withoutmutagen composemutagen-io/mutagen#238): does not cover thedevcontainer
workspaceFoldervariant — this ticket is the follow-up.5. Validation
coder_agent.dir = /home/coder/coder(dogfood "WriteCoder on Coder"): confirm current failure, then confirm the patched build installs
the agent and sync reaches "Watching".
workspaceFolder!=$HOME,coder_agent.dirunset. Confirm target selection (sub-agent vs parent), then confirm the fix.
dir=~/home) still works.ls -la ~/.mutagen/agents/on the remote shows the installed agent; no stray.mutagen-agent<UUID>temp files left under the working directory.6. Open questions / decisions before coding
micro-decision: confirm absolute-path resolution vs. a narrower variant with
Asher/Atif before opening the PR.
devcontainer sub-agent (its own
agentssh.Server, cwd =workspaceFolder,container == ""so SFTP is allowed). No separate "SFTP blocked" gap for thesub-agent path. (One caveat noted by the trace:
agent.godisablesa.devcontainersafter seeingParentID; it does not change the conclusion sincethe guard needs
container != "", which is empty here. Worth a quick runtimeconfirmation during implementation.)
~/before the agent sees it, so apkg/sftp~-expansion option cannot help the scp/Mutagen path.printf %s "$HOME") and define the fallback when it is empty/unresolvable.7. Deliverables
coder/mutagenPR: absolute-path agent bootstrap inpkg/agent/install.go+tests + release tag.
gs://coder-desktop/mutagen/<ver>/.coder-desktop-macosPR:.mutagenversionbump (+ proto regen only if the protosurface changed). Same bump for the Windows desktop client.
dir/workspaceFolderset,scp coder:~/foolands in thesession working directory (expected
#21194behavior); File Sync now works via theabsolute-path bootstrap.
coder/coderSFTP~-expansion change (ineffective).