Fix Windows relative bind mounts rejected for extended-length path prefix#51
Merged
Conversation
…efix On Windows, std's canonicalize returns the extended-length `\?\C:\...` form. Workspace::from_path stored that in Workspace::path, so a relative bind mount resolved onto it and was forwarded to Docker as `\?\C:\...`, which the daemon rejects (with a garbled `%!(EXTRA ...)` moby fmt artifact). The prefix also leaked into `eph check` and `eph status`. Canonicalize with dunce instead: it yields a plain `C:\...` path whenever one exists, fixing bind sources, display output, and compose/build current_dir in one place. On Unix it is std::fs::canonicalize. As a boundary guard, resolve_volume_spec is now fallible and rejects any host source that still carries `\?\` (the genuine long-path fallback) with an actionable message instead of forwarding a source Docker only rejects cryptically. The relative join uses to_str so a non-UTF-8 path errors rather than mounting a lossy source. Windows-only breaking change: the workspace id is derived from the path, so dropping `\?\` changes it. Containers, volumes, and state under the old id are orphaned and need manual cleanup (documented in the troubleshooting guide). Fixes #44 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Why
On Windows, a service with a relative bind mount (
volume=./seed:/docker-entrypoint-initdb.d) fails ateph up(issue #44):Workspace::from_pathcanonicalized withstd, which on Windows returns the extended-length\?\C:\...form and stores it inWorkspace::path. A relative bind resolves against that root, so eph forwarded\?\C:\...to Docker as the bind source, which the daemon rejects. (The%!(EXTRA ...)garble is an upstream mobyfmtartifact, reproducible with a baredocker run -v.) The prefix also leaked intoeph checkandeph statusoutput.What Changed
dunce::canonicalizeinWorkspace::from_pathandWatch::new. It returns a plainC:\...path whenever one exists (the common case), keeping\?\only for paths that genuinely require it. This fixes bind sources, display output, and compose/buildcurrent_dirat the source. On Unix it isstd::fs::canonicalize(no behavior change).resolve_volume_specis now fallible. A boundary guard (reject_verbatim_bind_source) rejects any host source still carrying\?\(the genuine long-path fallback) with an actionable message, instead of handing Docker a source it only rejects cryptically. The relative join usesto_str()so a non-UTF-8 path errors rather than silently mounting a lossy source.Designed and reviewed in collaboration with Codex (gpt-5.5).
Testing
cargo test --lib(96 pass, including two new regression tests: a clean Windows root resolves without the prefix, and a verbatim root is rejected),cargo test --doc(7 pass),cargo clippy --all-targets(clean)..eph(namedpgdata+volume=./seed:/docker-entrypoint-initdb.d).eph upnow succeeds (was a 500).docker inspectshows a clean bind source, and the seed table created by the bind-mounted init script exists in postgres.eph check/eph statusprintC:\Users\...with no\?\.User-Facing Or Compatibility Impact
eph check/eph statusno longer show the\?\prefix.\?\changes the id. Containers, named volumes, and the state directory created by an older build live under the previous id and are orphaned; they need manualdockercleanup. Named volumes did work before, so this only matters for services already running under an older build. Documented in the troubleshooting guide.volume=C:\data:/x) remain unsupported: they route to the named-volume branch and the drive-letter colon breakssplitn. That is a separate pre-existing bug, out of scope here; a real volume-spec parser is the proper fix.Fixes #44