{"record":{"id":"22e9bf76ee2aa2e3","repo":"hasura/graphql-engine","slug":"can-t-replace-q-in-q-it-is-not-a-subpath","errorCode":null,"errorMessage":"can't replace %q in %q, it is not a subpath","messagePattern":"can't replace %q in %q, it is not a subpath","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/plugins/util.go","lineNumber":286,"sourceCode":"\t\treturn \"\", false\n\t}\n\n\tif strings.HasPrefix(extendingPath, \"..\") {\n\t\treturn \"\", false\n\t}\n\n\treturn extendingPath, true\n}\n\n// 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.\nfunc ReplaceBase(path, old, replacement string) (string, error) {\n\tvar op errors.Op = \"plugins.ReplaceBase\"\n\n\textendingPath, ok := IsSubPath(old, path)\n\tif !ok {\n\t\treturn \"\", errors.E(\n\t\t\top,\n\t\t\tfmt.Errorf(\"can't replace %q in %q, it is not a subpath\", old, path),\n\t\t)\n\t}\n\n\treturn filepath.Join(replacement, extendingPath), nil\n}\n","sourceCodeStart":268,"sourceCodeEnd":292,"githubUrl":"https://github.com/hasura/graphql-engine/blob/724551b9ae87845594ef0408cff0e50eb6c90dc5/cli/plugins/util.go#L268-L292","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nnewPath, err := plugins.ReplaceBase(oldBase, newBase, \"./plugins/bin/tool\")\n// error: can't replace ... it is not a subpath\n\n// after\nabsPath, _ := filepath.Abs(\"./plugins/bin/tool\")\nnewPath, err := plugins.ReplaceBase(filepath.Clean(oldBase), newBase, absPath)","handlingStrategy":"validation","validationCode":"oldAbs, _ := filepath.Abs(old)\npathAbs, _ := filepath.Abs(path)\nif _, ok := plugins.IsSubPath(oldAbs, pathAbs); !ok {\n    return fmt.Errorf(\"path %s is outside base %s\", pathAbs, oldAbs)\n}\nnewPath, err := plugins.ReplaceBase(oldAbs, replacement, pathAbs)","typeGuard":"func underBase(base, p string) bool {\n    b, _ := filepath.Abs(base)\n    q, _ := filepath.Abs(p)\n    rel, err := filepath.Rel(b, q)\n    return err == nil && rel != \"..\" && !strings.HasPrefix(rel, \"..\"+string(filepath.Separator))\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["go","filepath","path-manipulation","subpath-check"],"backgroundTag":"path-not-under-base-directory","analyzedSha":"724551b9ae87845594ef0408cff0e50eb6c90dc5","analyzedAt":"2026-08-28T07:32:55.105Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}