hasura/graphql-engine · error
can't replace %q in %q, it is not a subpath
Error message
can't replace %q in %q, it is not a subpath
What it means
ReplaceBase rebuilds a path by swapping its base directory (old) for a replacement, but only after verifying via IsSubPath that the path actually lives under old. If path is not a subpath of old, there is no suffix to transplant and the function refuses, returning this error instead of producing a bogus path.
Source
Thrown at cli/plugins/util.go:286
return "", false
}
if strings.HasPrefix(extendingPath, "..") {
return "", false
}
return extendingPath, true
}
// ReplaceBase will return a replacement path with replacement as a base of the path instead of the old base. a/b/c, a, d -> d/b/c.
func ReplaceBase(path, old, replacement string) (string, error) {
var op errors.Op = "plugins.ReplaceBase"
extendingPath, ok := IsSubPath(old, path)
if !ok {
return "", errors.E(
op,
fmt.Errorf("can't replace %q in %q, it is not a subpath", old, path),
)
}
return filepath.Join(replacement, extendingPath), nil
}
View on GitHub (pinned to 724551b9ae)
Solutions
- Ensure both old and path are absolute and cleaned before calling: filepath.Abs + filepath.Clean on both.
- On Windows, normalize drive letters to the same case and use filepath.Clean to strip '..' segments.
- Verify the path actually lives under the old base with the same IsSubPath helper before calling ReplaceBase.
- If the paths legitimately differ, pick the correct base directory rather than forcing the replacement.
Example fix
// before
newPath, err := plugins.ReplaceBase(oldBase, newBase, "./plugins/bin/tool")
// error: can't replace ... it is not a subpath
// after
absPath, _ := filepath.Abs("./plugins/bin/tool")
newPath, err := plugins.ReplaceBase(filepath.Clean(oldBase), newBase, absPath) Defensive patterns
Strategy: validation
Validate before calling
oldAbs, _ := filepath.Abs(old)
pathAbs, _ := filepath.Abs(path)
if _, ok := plugins.IsSubPath(oldAbs, pathAbs); !ok {
return fmt.Errorf("path %s is outside base %s", pathAbs, oldAbs)
}
newPath, err := plugins.ReplaceBase(oldAbs, replacement, pathAbs) Type guard
func underBase(base, p string) bool {
b, _ := filepath.Abs(base)
q, _ := filepath.Abs(p)
rel, err := filepath.Rel(b, q)
return err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator))
} Prevention
- Always call filepath.Abs on both base and path before path surgery.
- On Windows, normalize drive-letter case before comparing paths.
- Avoid '..' segments in stored paths; run filepath.Clean.
When it happens
Trigger: Calling ReplaceBase(old, replacement, path) where path is outside the old directory tree: a relative-vs-absolute mismatch, differing drive letters on Windows, or path containing '..' segments that escape old.
Common situations: A migration/plugin path was resolved against a different working directory than expected (relative path like ./migrations vs absolute /home/user/migrations), or Windows drive-letter casing/quoting differences make two identical-looking paths compare as unrelated.
Related errors
- error building project metadata: %w
- exporting metadata from server: %w
- reading metadata from response: %w
- writing metadata to file: %w
- reading metadata file: %w
AI-assisted analysis of hasura/graphql-engine@724551b9ae (2026-08-28).
Data as JSON: /api/errors/22e9bf76ee2aa2e3.
Report an issue: GitHub.