{"record":{"id":"e24723029342d9de","repo":"caddyserver/caddy","slug":"s-e24723","errorCode":null,"errorMessage":"%s","messagePattern":"%s","errorType":"http","errorClass":"HandlerError","httpStatus":500,"severity":"info","filePath":"modules/caddyhttp/staticerror.go","lineNumber":108,"sourceCode":"\t\tdefault:\n\t\t\treturn d.Errf(\"unrecognized subdirective '%s'\", d.Val())\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (e StaticError) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {\n\trepl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)\n\n\tstatusCode := http.StatusInternalServerError\n\tif codeStr := e.StatusCode.String(); codeStr != \"\" {\n\t\tintVal, err := strconv.Atoi(repl.ReplaceAll(codeStr, \"\"))\n\t\tif err != nil {\n\t\t\treturn Error(http.StatusInternalServerError, err)\n\t\t}\n\t\tstatusCode = intVal\n\t}\n\treturn Error(statusCode, fmt.Errorf(\"%s\", repl.ReplaceKnown(e.Error, \"\")))\n}\n\n// Interface guard\nvar (\n\t_ MiddlewareHandler     = (*StaticError)(nil)\n\t_ caddyfile.Unmarshaler = (*StaticError)(nil)\n)\n","sourceCodeStart":90,"sourceCodeEnd":116,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/staticerror.go#L90-L116","documentation":"Not a defect: the static 'error' handler returns the configured message and status code to trigger Caddy's error chain. ServeHTTP resolves the StatusCode string and Error body through the replacer, then returns Error(statusCode, fmt.Errorf(\"%s\", body)). The only genuine failure path is when a placeholder-driven status code resolves to a non-integer, which yields HTTP 500 wrapping strconv.Atoi's error.","triggerScenarios":"Using the 'error' Caddyfile directive or http.handlers.error module: e.g. 'error 403 \"forbidden\"'. Failing path: statusCode contains a placeholder like {http.error.status} that resolves to non-numeric text at request time.","commonSituations":"Intentionally aborting requests with a fixed status inside routes; placeholder-based status that is empty or textual; users surprised to see their configured string surface as a Go error in logs.","solutions":["If 500 appears unexpectedly: make sure any placeholder in the status code always resolves to a 3-digit integer (or omit it)","Remember the configured body is returned as an error by design — pair it with handle_errors to render a custom response","For plain responses without error semantics, use 'respond' instead of 'error'"],"exampleFix":"# before\nerror {http.request.header.x-status} \"custom\"\n# header missing -> Atoi(\"\") fails -> 500\n\n# after\nerror 403 \"forbidden\"","handlingStrategy":"validation","validationCode":"// if the status uses placeholders, guarantee it resolves to an integer:\nresolved := repl.ReplaceAll(statusCodeStr, \"\")\nif n, err := strconv.Atoi(resolved); err != nil || n < 100 || n > 599 {\n    statusCode = http.StatusInternalServerError // explicit fallback, not accidental\n}","typeGuard":null,"tryCatchPattern":"// in handle_errors, inspect the returned error to branch:\nhandle_errors {\n    rewrite * /error.html\n    file_server\n}","preventionTips":["Use literal status codes with the error directive; avoid placeholders unless guaranteed numeric","Use 'respond' for plain responses; reserve 'error' for triggering error handling","Add an integration test asserting the final status code of error routes"],"tags":["caddy","error-handler","static-response","placeholders","by-design"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}