{"record":{"id":"34075fdf251a93d6","repo":"d2lang/d2","slug":"cannot-read-a-directory","errorCode":null,"errorMessage":"cannot read a directory","messagePattern":"cannot read a directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"lib/memfs/memfs.go","lineNumber":63,"sourceCode":"\ntype MemoryFileHandle struct {\n\t*MemoryFile\n\toffset int\n}\n\nfunc (mfs *MemoryFS) Open(name string) (fs.File, error) {\n\tfile, ok := mfs.files[filepath.Clean(name)]\n\tif !ok {\n\t\treturn nil, fs.ErrNotExist\n\t}\n\treturn &MemoryFileHandle{MemoryFile: file}, nil\n}\n\nfunc (fh *MemoryFileHandle) Stat() (fs.FileInfo, error) { return fh.MemoryFile, nil }\n\nfunc (fh *MemoryFileHandle) Read(b []byte) (int, error) {\n\tif fh.isDir {\n\t\treturn 0, errors.New(\"cannot read a directory\")\n\t}\n\tif fh.offset >= len(fh.content) {\n\t\treturn 0, io.EOF\n\t}\n\tn := copy(b, fh.content[fh.offset:])\n\tfh.offset += n\n\treturn n, nil\n}\n\nfunc (fh *MemoryFileHandle) Close() error { return nil }\n\nfunc (mf *MemoryFile) Stat() (fs.FileInfo, error) { return mf, nil }\nfunc (mf *MemoryFile) Read(b []byte) (int, error) {\n\tif mf.isDir {\n\t\treturn 0, errors.New(\"cannot read a directory\")\n\t}\n\tcopy(b, mf.content)\n\treturn len(mf.content), nil","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/lib/memfs/memfs.go#L45-L81","documentation":"MemoryFileHandle.Read returns this error when the handle refers to a directory in the in-memory filesystem. Directories have no byte content to read, so the Read fails immediately instead of returning garbage or EOF.","triggerScenarios":"Opening a directory entry from lib/memfs and calling Read on the resulting MemoryFileHandle; e.g. fh.Open on a path that is a directory, then fh.Read(buf).","commonSituations":"Walking a memfs tree and treating every entry as a file, using fs.ReadDir result incorrectly, or a path typo pointing at a directory instead of a file.","solutions":["Check entry.IsDir() (or Stat on the handle) before calling Read and use fs.ReadDir/ReadDirAll for directories","Fix the path to point to a file","Add a type-switch or stat check in the caller to branch between file reads and directory listings"],"exampleFix":"// before\nfh, _ := mfs.Open(\"assets/\")\nn, err := fh.Read(buf)\n// after\nfi, _ := fh.Stat()\nif fi.IsDir() {\n    entries, err := mfs.ReadDir(\"assets/\")\n} else {\n    n, err := fh.Read(buf)\n}","handlingStrategy":"type-guard","validationCode":"fi, err := mfs.Stat(path)\nif err == nil && fi.IsDir() {\n    // use ReadDir instead of Read\n}","typeGuard":"func isFile(n fs.FileInfo) bool { return n != nil && !n.IsDir() }","tryCatchPattern":"n, err := fh.Read(buf)\nif err != nil && err.Error() == \"cannot read a directory\" {\n    entries, lerr := mfs.ReadDir(path)\n    // handle directory listing\n}","preventionTips":["Stat entries and check IsDir() before reading","Use fs.ReadDir/fs.WalkDir for traversal instead of blind reads","Verify paths point to files in tests seeding memfs"],"tags":["go","filesystem","memfs","io"],"backgroundTag":"read-directory-as-file","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}