fix(ssh): use bare mock-agent name so the remote PATH resolves it

For SSH the agent runs on a remote host whose filesystem doesn't share
paths with the kandev backend's, so the host-absolute mock-agent path
configureMockAgent computes can't apply. Extend the bare-name branch in
MockAgent.BuildCommand to include RuntimeSSH (the SSH e2e image already
bakes mock-agent into /usr/local/bin) and drop the now-redundant SSH e2e
bind-mount that worked around this by mirroring the host path into the
container.
This commit is contained in:
José Almeida
2026-05-18 02:33:32 +01:00
parent cbc573b0a0
commit 628ceaabb5
3 changed files with 16 additions and 22 deletions
+9 -1
View File
@@ -6,6 +6,7 @@ import (
"time"
"github.com/kandev/kandev/internal/agent/usage"
"github.com/kandev/kandev/internal/agentruntime"
"github.com/kandev/kandev/pkg/agent"
)
@@ -139,12 +140,19 @@ func (a *MockAgent) BuildCommand(opts CommandOptions) Command {
// has /usr/local/bin on PATH and container.go bind-mounts the
// linux/amd64 mock-agent there, so the bare name resolves correctly.
//
// SSH is the same shape: the agent runs on a remote host whose
// filesystem doesn't share paths with the kandev backend's, so the
// host-absolute binary path can't apply. The SSH e2e image bakes
// mock-agent at /usr/local/bin/mock-agent; production SSH expects
// users to install agents into the remote's $PATH (real agents like
// Claude use `npx` and never hit this branch).
//
// For host runtimes (standalone) we prefer the absolute host path
// that configureMockAgent computed via os.Executable() — that's the
// only way the host path works in dev mode (where the shell's $PATH
// doesn't include apps/backend/bin). Fall back to the bare name when
// no path is set: e2e fixtures prepend the kandev bin dir to PATH.
if opts.Runtime.IsContainerized() {
if opts.Runtime.IsContainerized() || opts.Runtime == agentruntime.RuntimeSSH {
return Cmd(mockAgentDefaultID).Build()
}
binary := mockAgentDefaultID
@@ -36,11 +36,12 @@ func TestMockAgent_BuildCommand_NoResumeFlag(t *testing.T) {
// TestMockAgent_BuildCommand_ContainerizedUsesBareName pins the docker
// branch of BuildCommand: when opts.Runtime is containerized (docker,
// remote_docker, sprites), MockAgent must emit the bare binary name so
// PATH lookup inside the container resolves to the bind-mounted
// linux/amd64 mock-agent at /usr/local/bin. Regressing this would
// cause docker e2e to fail with `exec: "<host-abs-path>": not found`,
// which is exactly the bug commit `8518f65` fixed.
// remote_docker, sprites) or SSH, MockAgent must emit the bare binary
// name so PATH lookup on the remote resolves to the bind-mounted (or
// pre-baked) linux/amd64 mock-agent at /usr/local/bin. Regressing this
// would cause docker e2e to fail with `exec: "<host-abs-path>": not
// found` (the bug commit `8518f65` fixed) and SSH e2e with the same
// symptom on the remote host.
func TestMockAgent_BuildCommand_ContainerizedUsesBareName(t *testing.T) {
a := NewMockAgent()
a.SetBinaryPath("/Users/dev/kandev/apps/backend/bin/mock-agent")
@@ -48,6 +49,7 @@ func TestMockAgent_BuildCommand_ContainerizedUsesBareName(t *testing.T) {
agentruntime.RuntimeDocker,
agentruntime.RuntimeRemoteDocker,
agentruntime.RuntimeSprites,
agentruntime.RuntimeSSH,
} {
t.Run(string(rt), func(t *testing.T) {
cmd := a.BuildCommand(CommandOptions{Runtime: rt})
-16
View File
@@ -53,20 +53,6 @@ export function startSSHServer(
const publicKeyFile = `${identityFile}.pub`;
generateKeypair(identityFile);
// kandev's orchestrator builds the agent command using KANDEV_MOCK_AGENT_LINUX_BINARY,
// which is set to the *host* absolute path of mock-agent-linux-amd64.
// Docker e2e gets away with using that path inside the container because
// it bind-mounts the same host file at the same path; for SSH e2e we have
// to do the equivalent — otherwise the kandev backend tells agentctl on
// the remote to exec a path that doesn't exist there.
const mockAgentHostPath = path.resolve(__dirname, "../../../backend/bin/mock-agent-linux-amd64");
if (!fs.existsSync(mockAgentHostPath)) {
throw new Error(
`mock-agent-linux-amd64 not found at ${mockAgentHostPath}; ` +
`build it with 'make -C apps/backend build-mock-agent-linux'.`,
);
}
// The container reads /authorized_keys on entry, so mount the public key
// into that path read-only.
removeContainerIfExists(containerName);
@@ -96,8 +82,6 @@ export function startSSHServer(
"-v",
`${publicKeyFile}:/authorized_keys:ro`,
"-v",
`${mockAgentHostPath}:${mockAgentHostPath}:ro`,
"-v",
`${remoteHome}:/home/kandev/.kandev`,
imageTag,
],