fix(remote): keep Codeman's pane command away from the remote default-shell

Handing the launch to bash verbatim exposed `"${SHELL:-/bin/sh}"` to the far
end for the first time. tmux runs a one-argument command through the remote
default-shell, not /bin/sh as the comments said, and fish refuses `${...}`:
on a fish host, or a bash login with `set -g default-shell fish`, every new
pane died with status 127. master only survived because the local sh had
already rewritten that token to this host's shell path.

Codeman's own text now goes to `/bin/sh -c` as separate tmux arguments, which
tmux execs without a shell. A commands.* override stays one argument and keeps
the default-shell it was written for: under `/bin/sh` a `source` or `[[` in an
override dies with 127 on bash and zsh hosts.

The tests read the argv tmux would receive through a real sh, so they need no
fish: every stock launch must reach /bin/sh and an override must not. The
docker half of bashCArg was pinned by nothing, so it gets its own test.

Comments that described the removed double-quote layer are corrected, and
remote-hosts.ts no longer claims overrides are seeded from the default text
or that they rescue nushell and elvish.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-11-06 00:59:57 +01:00
1 parent 21256202ba
commit 012ca2e971
9 files changed
+190 -37

No files matched your search

+32
View File
@@ -939,6 +939,38 @@ describe('TmuxManager (unit)', () => {
}
});
it('hands a docker override to bash with $HOME unexpanded', async () => {
const NonTestTmuxManager = await importWithTmuxCommandsEnabled();
const nonTestManager = new NonTestTmuxManager();
try {
await nonTestManager.createSession({
sessionId: 'abc12345-1234-5678-90ab-cdef12345680',
workingDir: '/w',
mode: 'shell',
docker: {
hostId: 'h',
label: 'L',
engine: 'docker',
image: 'codeman/agent:base',
containerName: 'codeman-case-x',
hostWorkspacePath: '/w',
containerWorkdir: '/w',
commands: { shell: 'echo CONTAINER_HOME=$HOME; exec sleep 30' },
} as never,
});
const launch = [...mockedExecSync.mock.calls, ...vi.mocked(exec).mock.calls]
.map(([cmd]) => cmd)
.find((cmd) => typeof cmd === 'string' && cmd.includes(' respawn-pane '));
const sent = await bashArgAfterLocalShell(launch as string);
expect(sent).toContain('docker');
expect(sent).toContain('CONTAINER_HOME=$HOME');
expect(sent).not.toContain('local-home-xyz');
} finally {
nonTestManager.destroy();
}
});
it('changes the global history default on tmux versions that cannot resize panes', async () => {
const NonTestTmuxManager = await importWithTmuxCommandsEnabled();
const nonTestManager = new NonTestTmuxManager();