helm/helm · error
file %s already exists and is not a directory
Error message
file %s already exists and is not a directory
What it means
SaveDir() (internal/chart/v3/util/save.go) writes a chart into dest/<chart-name>/ and first stats that target: if a filesystem entry exists at that path but is a regular file rather than a directory, it aborts with this error. This prevents MkdirAll from either failing confusingly or, worse, writing chart files through an existing file path. The chart name itself was already validated by validateName before this point.
Source
Thrown at internal/chart/v3/util/save.go:50
chart "helm.sh/helm/v4/internal/chart/v3"
"helm.sh/helm/v4/pkg/chart/common"
)
var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=")
// SaveDir saves a chart as files in a directory.
//
// This takes the chart name, and creates a new subdirectory inside of the given dest
// directory, writing the chart's contents to that subdirectory.
func SaveDir(c *chart.Chart, dest string) error {
// Create the chart directory
err := validateName(c.Name())
if err != nil {
return err
}
outdir := filepath.Join(dest, c.Name())
if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() {
return fmt.Errorf("file %s already exists and is not a directory", outdir)
}
if err := os.MkdirAll(outdir, 0o755); err != nil {
return err
}
// Save the chart file.
if err := SaveChartfile(filepath.Join(outdir, ChartfileName), c.Metadata); err != nil {
return err
}
// Save values.yaml
for _, f := range c.Raw {
if f.Name == ValuesfileName {
vf := filepath.Join(outdir, ValuesfileName)
if err := writeFile(vf, f.Data); err != nil {
return err
}
}View on GitHub (pinned to 2a29f1770b)
Solutions
- Remove or rename the offending file: `rm <dest>/<chart-name>` (or move it elsewhere), then retry SaveDir
- Point SaveDir at a fresh destination directory (a new temp dir is the safest)
- If the collision comes from case-insensitive filesystems, align the chart name's casing with the existing directory or remove the stale entry
Example fix
# before: dest/mychart is a regular file, SaveDir fails rm dest/mychart # after: retry now that the path is free (or use a clean dir) util.SaveDir(c, "dest")
Defensive patterns
Strategy: validation
Validate before calling
// ensure SaveDir's target is free or a directory before calling it
func ensureSaveDirFree(dest, chartName string) error {
outdir := filepath.Join(dest, chartName)
if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() {
return fmt.Errorf("%s exists and is a file; remove it first", outdir)
}
return nil
} Type guard
func pathIsDirOrMissing(p string) bool {
fi, err := os.Stat(p)
return err != nil || fi.IsDir()
} Try / catch
if err := util.SaveDir(c, dest); err != nil {
if strings.Contains(err.Error(), "already exists and is not a directory") {
os.Remove(filepath.Join(dest, c.Name())) // only if safe in your context
return util.SaveDir(c, dest)
}
return err
} Prevention
- Save charts into fresh temp dirs per run (os.MkdirTemp) instead of shared destinations
- On case-insensitive filesystems (macOS/Windows), avoid chart names differing only by case
- Clean stale outputs in CI between runs
When it happens
Trigger: Calling util.SaveDir(c, dest) when dest/<c.Name()> exists as a file — e.g. a leftover 'mychart' file from a previous run, a tarball saved without its extension, or a case-insensitive filesystem collision ('MyChart' vs 'mychart').
Common situations: Re-running `helm chart save` style operations or SDK SaveDir calls after an earlier partial run left a file behind; pulling charts into a directory where a same-named .yaml/.txt file exists; macOS/Windows case-insensitivity turning a previously distinct name into a collision.
Related errors
- cannot load a directory
- chart file %q is larger than the maximum file size %d
- error reading %s: %w
- %q is not a directory
- no %s exists in directory %q
AI-assisted analysis of helm/helm@2a29f1770b (2026-08-15).
Data as JSON: /api/errors/ff09c4786ceedc1a.
Report an issue: GitHub.