2.9 KiB
Mount Tilde Expansion
Date: 2026-05-23 Status: Approved
Problem
Mount paths in .sandcage.yml use ~ for portability (e.g. ~/.ssh:/home/agent/.ssh:ro).
Sandcage passes these strings verbatim as -v flags to docker compose run via Rust's
Command::new(), which does not invoke a shell. Since ~ is a shell expansion feature,
Docker receives the literal string ~/.ssh and either:
- Windows: Errors with "invalid characters for a local volume name"
- Linux/macOS: Creates a literal
~directory relative to CWD
Platform Matrix
| Environment | dirs::home_dir() |
Docker path format | Notes |
|---|---|---|---|
| Native Windows (PowerShell/cmd) | C:\Users\<user> |
C:/Users/<user>/... |
Forward slashes required |
| MINGW / Git Bash | C:\Users\<user> (via Rust) |
C:/Users/<user>/... |
Rust bypasses MSYS path mangling |
| WSL (Linux binary) | /home/<user> |
/home/<user>/... |
Native Linux paths |
| Native Linux | /home/<user> |
/home/<user>/... |
Straightforward |
| macOS | /Users/<user> |
/Users/<user>/... |
Straightforward |
Key insight: Rust's Command::new() spawns Docker directly — no shell involved — so
the environment (PowerShell, Git Bash, etc.) is irrelevant. dirs::home_dir() returns
the correct native path on every platform.
Design
Approach: Expand at mount-pass-through time
Config files keep the portable ~ form. Expansion happens only when building Docker
CLI args.
Implementation
Add expand_mount_path(mount: &str) -> String in docker.rs:
- Split mount on
:to extract host path, container path, and optional mode. - If host path starts with
~/or equals~, replace the~prefix withdirs::home_dir(). - On Windows (
cfg!(windows)), normalize host path separators to forward slashes. - Rejoin parts with
:and return.
Call site: build_run_args() in docker.rs, replacing the current
args.push(mount.clone()) with args.push(expand_mount_path(mount)).
Edge Cases
| Input | Output (Linux) | Output (Windows) |
|---|---|---|
~/.ssh:/home/agent/.ssh:ro |
/home/user/.ssh:/home/agent/.ssh:ro |
C:/Users/user/.ssh:/home/agent/.ssh:ro |
~:/container |
/home/user:/container |
C:/Users/user:/container |
/absolute:/container:ro |
/absolute:/container:ro (unchanged) |
/absolute:/container:ro (unchanged) |
./relative:/container |
./relative:/container (unchanged) |
./relative:/container (unchanged) |
Not in Scope
$HOME/${HOME}expansion~otheruser/expansion- Environment variable substitution in mount paths
Testing
- Unit tests for
expand_mount_pathcovering all edge cases above. - Update existing
build_run_argstests that assert literal~/.sshto assert the expanded absolute path.
Files Changed
crates/sandcage/src/docker.rs— addexpand_mount_path, call it inbuild_run_args- Existing tests in
docker.rs— update mount assertions