{"record":{"id":"a858ad9cb5346eaa","repo":"caddyserver/caddy","slug":"precompressor-does-not-specify-a-suffix-value","errorCode":null,"errorMessage":"precompressor does not specify a Suffix value","messagePattern":"precompressor does not specify a Suffix value","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/fileserver/staticfiles.go","lineNumber":237,"sourceCode":"\t}\n\n\t// support precompressed sidecar files\n\tmods, err := ctx.LoadModule(fsrv, \"PrecompressedRaw\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"loading encoder modules: %v\", err)\n\t}\n\tfor modName, modIface := range mods.(map[string]any) {\n\t\tp, ok := modIface.(encode.Precompressed)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"module %s is not precompressor\", modName)\n\t\t}\n\t\tae := p.AcceptEncoding()\n\t\tif ae == \"\" {\n\t\t\treturn fmt.Errorf(\"precompressor does not specify an Accept-Encoding value\")\n\t\t}\n\t\tsuffix := p.Suffix()\n\t\tif suffix == \"\" {\n\t\t\treturn fmt.Errorf(\"precompressor does not specify a Suffix value\")\n\t\t}\n\t\tif _, ok := fsrv.precompressors[ae]; ok {\n\t\t\treturn fmt.Errorf(\"precompressor already added: %s\", ae)\n\t\t}\n\t\tif fsrv.precompressors == nil {\n\t\t\tfsrv.precompressors = make(map[string]encode.Precompressed)\n\t\t}\n\t\tfsrv.precompressors[ae] = p\n\t}\n\n\tif fsrv.Browse != nil {\n\t\t// check sort options\n\t\tfor idx, sortOption := range fsrv.Browse.SortOptions {\n\t\t\tswitch idx {\n\t\t\tcase 0:\n\t\t\t\tif sortOption != sortByName && sortOption != sortByNameDirFirst && sortOption != sortBySize && sortOption != sortByTime {\n\t\t\t\t\treturn fmt.Errorf(\"the first option must be one of the following: %s, %s, %s, %s, but got %s\", sortByName, sortByNameDirFirst, sortBySize, sortByTime, sortOption)\n\t\t\t\t}","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/fileserver/staticfiles.go#L219-L255","documentation":"Returned in FileServer.Provision when a precompressor module's Suffix() returns an empty string. The suffix (e.g. \".gz\", \".br\") is appended to the original filename to locate the precompressed sidecar file; an empty suffix would make the server stat the original file itself, so provisioning fails.","triggerScenarios":"A custom precompressor plugin whose Suffix() method returns \"\" — unimplemented stub, or a user-configured suffix field left blank.","commonSituations":"Plugin scaffolding with zero-value returns; forgetting that Suffix must include the leading dot; config-driven suffix fields left unset.","solutions":["Return a non-empty suffix including the dot, e.g. \".gz\" or \".foo\".","If suffix comes from plugin config, set the field or validate it non-empty in the plugin's Validate().","Rebuild the plugin after the fix with xcaddy."],"exampleFix":"// before\nfunc (Foo) Suffix() string { return \"\" }\n// after\nfunc (Foo) Suffix() string { return \".foo\" }","handlingStrategy":"validation","validationCode":"// In your plugin's Validate(): require a dot-prefixed, non-empty suffix\nfunc (m *MyPrecompressor) Validate() error {\n    s := m.Suffix()\n    if s == \"\" || !strings.HasPrefix(s, \".\") {\n        return fmt.Errorf(\"suffix must be non-empty and start with '.'\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Document that Suffix() must include the leading dot (e.g. '.gz').","Unit-test the module contract (AcceptEncoding/Suffix) as part of the plugin build.","Validate plugin config early so failures surface as config errors, not runtime surprises."],"tags":["caddy","fileserver","precompressed","module-system","validation"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}