golang/go · error · fs.PathError

not a directory

Error message

not a directory

What it means

Returned by cache.Open(dir) when os.Stat(dir) succeeds but info.IsDir() is false — i.e. GOCACHE points at an existing file, not a directory. The disk cache needs a directory because it creates 256 sub-buckets (00..ff) under it. Wrapping as fs.PathError{Op:"open"} mirrors the stdlib open semantics.

Source

Thrown at src/cmd/go/internal/cache/cache.go:101

// Open opens and returns the cache in the given directory.
//
// It is safe for multiple processes on a single machine to use the
// same cache directory in a local file system simultaneously.
// They will coordinate using operating system file locks and may
// duplicate effort but will not corrupt the cache.
//
// However, it is NOT safe for multiple processes on different machines
// to share a cache directory (for example, if the directory were stored
// in a network file system). File locking is notoriously unreliable in
// network file systems and may not suffice to protect the cache.
func Open(dir string) (*DiskCache, error) {
	info, err := os.Stat(dir)
	if err != nil {
		return nil, err
	}
	if !info.IsDir() {
		return nil, &fs.PathError{Op: "open", Path: dir, Err: fmt.Errorf("not a directory")}
	}
	for i := 0; i < 256; i++ {
		name := filepath.Join(dir, fmt.Sprintf("%02x", i))
		if err := os.MkdirAll(name, 0o777); err != nil {
			return nil, err
		}
	}
	c := &DiskCache{
		dir: dir,
		now: time.Now,
	}
	return c, nil
}

// fileName returns the name of the file corresponding to the given id.
func (c *DiskCache) fileName(id [HashSize]byte, key string) string {
	return filepath.Join(c.dir, fmt.Sprintf("%02x", id[0]), fmt.Sprintf("%x", id)+"-"+key)
}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Remove the offending file at the GOCACHE path so the cache can create the directory itself.
  2. Point GOCACHE at an existing (or creatable) directory path.
  3. Run `go clean -cache` after fixing the path to start fresh.
  4. Audit provisioning scripts that touch the cache path.

Example fix

// before: GOCACHE points at a regular file
$ GOCACHE=$HOME/.cache/go-build go build .
// -> open ... not a directory

// after: remove the file, let go recreate the directory
$ rm -f "$HOME/.cache/go-build"
$ GOCACHE=$HOME/.cache/go-build go build .
Defensive patterns

Strategy: validation

Validate before calling

if fi, err := os.Stat(dir); err == nil && !fi.IsDir() {
    return fmt.Errorf("refusing to use %s: not a directory", dir)
}
c, err := cache.Open(dir)

Try / catch

c, err := cache.Open(dir)
if err != nil {
    var perr *fs.PathError
    if errors.As(err, &perr) && perr.Err.Error() == "not a directory" {
        // remove the offending file and retry once
        _ = os.Remove(dir)
        c, err = cache.Open(dir)
    }
}

Prevention

When it happens

Trigger: GOCACHE (or a caller-supplied cache dir) resolves to a regular file or a symlink to a file; cache.Open stats it, sees it is not a directory, and aborts before MkdirAll runs.

Common situations: A leftover file (e.g. `touch ~/.cache/go-build` by accident); GOCACHE typo colliding with an existing file; provisioning scripts that created a marker file at the cache path.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/82411a337a1b2019. Report an issue: GitHub.