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

  1. Ensure both old and path are absolute and cleaned before calling: filepath.Abs + filepath.Clean on both.
  2. On Windows, normalize drive letters to the same case and use filepath.Clean to strip '..' segments.
  3. Verify the path actually lives under the old base with the same IsSubPath helper before calling ReplaceBase.
  4. 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

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


AI-assisted analysis of hasura/graphql-engine@724551b9ae (2026-08-28). Data as JSON: /api/errors/22e9bf76ee2aa2e3. Report an issue: GitHub.