{"record":{"id":"caac24cce1cb0618","repo":"valyala/fasthttp","slug":"directory-with-unexpected-suffix-found-q-suffix","errorCode":null,"errorMessage":"directory with unexpected suffix found: %q: suffix: %q","messagePattern":"directory with unexpected suffix found: %q: suffix: %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"fs.go","lineNumber":1905,"sourceCode":"\n\t\t// If the file is not found and the path is empty, let's return errDirIndexRequired error.\n\t\tif filePath == \"\" && (errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrInvalid)) {\n\t\t\treturn nil, errDirIndexRequired\n\t\t}\n\n\t\treturn nil, err\n\t}\n\n\tfileInfo, err := f.Stat()\n\tif err != nil {\n\t\t_ = f.Close()\n\t\treturn nil, fmt.Errorf(\"cannot obtain info for file %q: %w\", filePath, err)\n\t}\n\n\tif fileInfo.IsDir() {\n\t\t_ = f.Close()\n\t\tif mustCompress {\n\t\t\treturn nil, fmt.Errorf(\"directory with unexpected suffix found: %q: suffix: %q\",\n\t\t\t\tfilePath, h.compressedFileSuffixes[fileEncoding])\n\t\t}\n\t\treturn nil, errDirIndexRequired\n\t}\n\n\tif mustCompress {\n\t\tfileInfoOriginal, err := fs.Stat(h.filesystem, filePathOriginal)\n\t\tif err != nil {\n\t\t\t_ = f.Close()\n\t\t\treturn nil, fmt.Errorf(\"cannot obtain info for original file %q: %w\", filePathOriginal, err)\n\t\t}\n\n\t\t// Only re-create the compressed file if there was more than a second between the mod times.\n\t\t// On macOS the gzip seems to truncate the nanoseconds in the mod time causing the original file\n\t\t// to look newer than the gzipped file.\n\t\tif fileInfoOriginal.ModTime().Sub(fileInfo.ModTime()) >= time.Second {\n\t\t\t// The compressed file became stale. Re-create it.\n\t\t\t_ = f.Close()","sourceCodeStart":1887,"sourceCodeEnd":1923,"githubUrl":"https://github.com/valyala/fasthttp/blob/c96f600972c6f4a7a30d664257b340ebe9d60124/fs.go#L1887-L1923","documentation":"The FS handler opened a path that turned out to be a directory even though its name carries the compressed-file suffix (e.g. .gz/.br). fasthttp refuses to serve a directory as a compressed file because suffix and content contradict. Depending on mustCompress it returns this error, or errDirIndexRequired to fall back to directory index handling.","triggerScenarios":"Request path resolves to a directory whose name ends in h.compressedFileSuffixes[fileEncoding] while mustCompress is true — e.g. a literal directory named 'data.gz' exists and FS is configured with Compress/CompressBrotli.","commonSituations":"Accidentally creating a directory named like a gzip asset (mkdir out/app.js.gz instead of gzip); build scripts leaving compressed-named dirs; path rewrite rules mapping routes to directories with such names.","solutions":["Remove or rename the directory that carries a compressed suffix (e.g. 'app.js.gz')","Rebuild static assets so .gz entries are real files, not directories","Adjust Root/PathRewrite so the resolved path points at a file","Disable compression options if you do not intend to serve pre-compressed files"],"exampleFix":"// before\n$ mkdir dist/bundle.js.gz\n// after\n$ gzip -k dist/bundle.js  # creates a real file dist/bundle.js.gz","handlingStrategy":"validation","validationCode":"if fi, err := os.Stat(path); err == nil && fi.IsDir() { return 404 } // also ensure no dir ends in .gz/.br","typeGuard":null,"tryCatchPattern":"if err := serveFS(ctx); err != nil { if strings.Contains(err.Error(), \"unexpected suffix\") { /* fix path or 404 */ } }","preventionTips":["Never name directories with .gz/.br suffixes","Verify build output with file(1) — compressed entries must be regular files","Test static file serving after build changes"],"tags":["filesystem","fasthttp","compression","static-files"],"backgroundTag":"directory-has-compressed-suffix","analyzedSha":"c96f600972c6f4a7a30d664257b340ebe9d60124","analyzedAt":"2026-08-31T22:48:28.265Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}