Skip to content

Commit 8594d50

Browse files
committed
🤖 Fix tilde (~/) path expansion in SSH exec
Fix bug where tilde paths like ~/cmux/project didn't work for SSH workspaces. Problem: - Bash doesn't expand ~ when it's inside quotes - `cd "~/path"` fails, but `cd ~/path` works - Previous code did: `cd ${JSON.stringify(cwd)}` which quoted the path - Tests used absolute paths (/home/...) so bug was not caught Solution: - Expand ~/path to $HOME/path before quoting - `cd "$HOME/path"` works because $HOME expands inside quotes - Only affects SSH runtime (local runtime doesn't use exec) Test coverage: - Added new test: "handles tilde (~/) paths correctly (SSH only)" - Uses ~/workspace/... instead of absolute path - All 14 integration tests now passing Why tests didn't catch it: - SSH test fixture used hardcoded absolute path: /home/testuser/workspace - Real users often use tilde paths in config (~/projects, ~/cmux, etc.) - New test ensures tilde paths work end-to-end _Generated with `cmux`_
1 parent 96ff890 commit 8594d50

File tree

2 files changed

+55
-1
lines changed

2 files changed

+55
-1
lines changed

src/runtime/SSHRuntime.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,14 @@ export class SSHRuntime implements Runtime {
6262
envPrefix = `export ${envPairs}; `;
6363
}
6464

65+
// Expand ~/path to $HOME/path before quoting (~ doesn't expand in quotes)
66+
let cwd = options.cwd;
67+
if (cwd.startsWith("~/")) {
68+
cwd = "$HOME/" + cwd.slice(2);
69+
}
70+
6571
// Build full command with cwd and env
66-
const fullCommand = `cd ${JSON.stringify(options.cwd)} && ${envPrefix}${command}`;
72+
const fullCommand = `cd ${JSON.stringify(cwd)} && ${envPrefix}${command}`;
6773

6874
// Wrap command in bash to ensure bash execution regardless of user's default shell
6975
// This prevents issues with fish, zsh, or other non-bash shells

tests/ipcMain/createWorkspace.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,54 @@ exit 1
489489
},
490490
TEST_TIMEOUT_MS
491491
);
492+
493+
test.concurrent(
494+
"handles tilde (~/) paths correctly (SSH only)",
495+
async () => {
496+
const env = await createTestEnvironment();
497+
const tempGitRepo = await createTempGitRepo();
498+
499+
try {
500+
const branchName = generateBranchName("tilde-test");
501+
const trunkBranch = await detectDefaultTrunkBranch(tempGitRepo);
502+
503+
// Use ~/workspace/... path instead of absolute path
504+
const tildeRuntimeConfig: RuntimeConfig = {
505+
type: "ssh",
506+
host: `testuser@localhost`,
507+
workdir: `~/workspace/${branchName}`,
508+
identityFile: sshConfig!.privateKeyPath,
509+
port: sshConfig!.port,
510+
};
511+
512+
const { result, cleanup } = await createWorkspaceWithCleanup(
513+
env,
514+
tempGitRepo,
515+
branchName,
516+
trunkBranch,
517+
tildeRuntimeConfig
518+
);
519+
520+
expect(result.success).toBe(true);
521+
if (!result.success) {
522+
throw new Error(`Failed to create workspace with tilde path: ${result.error}`);
523+
}
524+
525+
// Wait for init to complete
526+
await new Promise((resolve) => setTimeout(resolve, getInitWaitTime()));
527+
528+
// Verify workspace exists
529+
expect(result.metadata.id).toBeDefined();
530+
expect(result.metadata.namedWorkspacePath).toBeDefined();
531+
532+
await cleanup();
533+
} finally {
534+
await cleanupTestEnvironment(env);
535+
await cleanupTempGitRepo(tempGitRepo);
536+
}
537+
},
538+
TEST_TIMEOUT_MS
539+
);
492540
}
493541

494542
});

0 commit comments

Comments
 (0)