{"record":{"id":"82411a337a1b2019","repo":"golang/go","slug":"not-a-directory-82411a","errorCode":null,"errorMessage":"not a directory","messagePattern":"not a directory","errorType":"error_code","errorClass":"fs.PathError","httpStatus":null,"severity":"error","filePath":"src/cmd/go/internal/cache/cache.go","lineNumber":101,"sourceCode":"\n// Open opens and returns the cache in the given directory.\n//\n// It is safe for multiple processes on a single machine to use the\n// same cache directory in a local file system simultaneously.\n// They will coordinate using operating system file locks and may\n// duplicate effort but will not corrupt the cache.\n//\n// However, it is NOT safe for multiple processes on different machines\n// to share a cache directory (for example, if the directory were stored\n// in a network file system). File locking is notoriously unreliable in\n// network file systems and may not suffice to protect the cache.\nfunc Open(dir string) (*DiskCache, error) {\n\tinfo, err := os.Stat(dir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !info.IsDir() {\n\t\treturn nil, &fs.PathError{Op: \"open\", Path: dir, Err: fmt.Errorf(\"not a directory\")}\n\t}\n\tfor i := 0; i < 256; i++ {\n\t\tname := filepath.Join(dir, fmt.Sprintf(\"%02x\", i))\n\t\tif err := os.MkdirAll(name, 0o777); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tc := &DiskCache{\n\t\tdir: dir,\n\t\tnow: time.Now,\n\t}\n\treturn c, nil\n}\n\n// fileName returns the name of the file corresponding to the given id.\nfunc (c *DiskCache) fileName(id [HashSize]byte, key string) string {\n\treturn filepath.Join(c.dir, fmt.Sprintf(\"%02x\", id[0]), fmt.Sprintf(\"%x\", id)+\"-\"+key)\n}","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/cmd/go/internal/cache/cache.go#L83-L119","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Remove the offending file at the GOCACHE path so the cache can create the directory itself.","Point GOCACHE at an existing (or creatable) directory path.","Run `go clean -cache` after fixing the path to start fresh.","Audit provisioning scripts that touch the cache path."],"exampleFix":"// before: GOCACHE points at a regular file\n$ GOCACHE=$HOME/.cache/go-build go build .\n// -> open ... not a directory\n\n// after: remove the file, let go recreate the directory\n$ rm -f \"$HOME/.cache/go-build\"\n$ GOCACHE=$HOME/.cache/go-build go build .","handlingStrategy":"validation","validationCode":"if fi, err := os.Stat(dir); err == nil && !fi.IsDir() {\n    return fmt.Errorf(\"refusing to use %s: not a directory\", dir)\n}\nc, err := cache.Open(dir)","typeGuard":null,"tryCatchPattern":"c, err := cache.Open(dir)\nif err != nil {\n    var perr *fs.PathError\n    if errors.As(err, &perr) && perr.Err.Error() == \"not a directory\" {\n        // remove the offending file and retry once\n        _ = os.Remove(dir)\n        c, err = cache.Open(dir)\n    }\n}","preventionTips":["Validate GOCACHE points at a directory in provisioning scripts.","Never touch a marker file at the cache path.","Run `go env GOCACHE` to confirm the resolved path."],"tags":["cache","filesystem","configuration","gocache"],"backgroundTag":null,"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}