golang/go · error
replacement directory %s: %w
Error message
replacement directory %s: %w
What it means
Sibling of 1063: a local replace directory failed fsys.Stat for a reason OTHER than os.ErrNotExist (e.g. permission denied, I/O error). The original error is wrapped with %w so it can be unwrapped by callers. Carried through module.VersionError so the offending module version is identified.
Source
Thrown at src/cmd/go/internal/modload/import.go:811
dir = r.Path
if !filepath.IsAbs(dir) {
dir = filepath.Join(replaceRelativeTo(ld), dir)
}
// Ensure that the replacement directory actually exists:
// dirInModule does not report errors for missing modules,
// so if we don't report the error now, later failures will be
// very mysterious.
if _, err := fsys.Stat(dir); err != nil {
// TODO(bcmills): We should also read dir/go.mod here and check its Go version,
// and return a gover.TooNewError if appropriate.
if os.IsNotExist(err) {
// Semantically the module version itself “exists” — we just don't
// have its source code. Remove the equivalence to os.ErrNotExist,
// and make the message more concise while we're at it.
err = fmt.Errorf("replacement directory %s does not exist", r.Path)
} else {
err = fmt.Errorf("replacement directory %s: %w", r.Path, err)
}
return dir, true, module.VersionError(mod, err)
}
return dir, true, nil
}
mod = r
}
if mustHaveSums(ld) && !modfetch.HaveSum(ld.Fetcher(), mod) {
return "", false, module.VersionError(mod, &sumMissingError{})
}
dir, err = ld.Fetcher().Download(ctx, mod)
return dir, false, err
}
// mustHaveSums reports whether we require that all checksums
// needed to load or build packages are already present in the go.sum file.View on GitHub (pinned to b6b368adc5)
Solutions
- Check permissions on every component of the path: chmod/chown so the Go process can stat it.
- Repair or remove dangling symlinks in the replace path.
- Verify the volume/mount holding the target is actually mounted in CI containers.
- Point the replace at a readable location, or switch to a versioned replace.
Example fix
// before replace example.com/foo => /mnt/foo // /mnt/foo: permission denied // error: replacement directory /mnt/foo: permission denied // after $ sudo chown -R $USER /mnt/foo // or fix the mount / mode $ go build ./...
Defensive patterns
Strategy: validation
Validate before calling
// Validate local replace targets are stat-able (not just present).
data, _ := os.ReadFile("go.mod")
f, _ := modfile.Parse("go.mod", data, nil)
for _, r := range f.Replace {
if r.New.Version == "" {
if _, err := os.Stat(r.New.Path); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("replace target %s unreadable: %w", r.New.Path, err)
}
}
} Try / catch
out, err := exec.Command("go", "build", "./...").CombinedOutput()
if err != nil && bytes.Contains(out, []byte("replacement directory")) {
// Could be 1063 (missing) or 1064 (perm/io). Surface wrapped cause.
var pathErr *os.PathError
if errors.As(errors.Unwrap(err), &pathErr) {
log.Printf("fix permissions on %s: %v", pathErr.Path, pathErr.Err)
}
return err
}
return err Prevention
- Ensure the Go process has read+execute on every path component of replace targets.
- In containers, verify bind-mounts for replace targets before building.
- Avoid replace paths that traverse temp or removable media.
- Pre-build check: os.Stat each local replace path.
When it happens
Trigger: go.mod has a local replace directive and fsys.Stat(dir) returns a non-NotExist error: EACCES (permission denied), EIO, a broken symlink, or a path crossing an unreadable mount.
Common situations: Replace target on a mounted volume that is not mounted; file mode / ownership blocks the Go process; dangling symlink in the replace path; container where the bind-mount is missing.
Related errors
- replacement directory %s does not exist
- reading go.work: %w
- error reading -outfilelist file %q: %v
- error reading pkgconfig file %q: %v
- can't read %q: %v
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/086efda56542383f.
Report an issue: GitHub.