{"record":{"id":"391ef0c4350ef5e6","repo":"caddyserver/caddy","slug":"cannot-specify-both-a-substring-search-and-a-regul","errorCode":null,"errorMessage":"cannot specify both a substring search and a regular expression search for field '%s'","messagePattern":"cannot specify both a substring search and a regular expression search for field '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/headers/headers.go","lineNumber":180,"sourceCode":"// containsPlaceholders checks if the string contains Caddy placeholder syntax {key}\nfunc containsPlaceholders(s string) bool {\n\t_, after, ok := strings.Cut(s, \"{\")\n\tif !ok {\n\t\treturn false\n\t}\n\tcloseIdx := strings.Index(after, \"}\")\n\tif closeIdx == -1 {\n\t\treturn false\n\t}\n\t// Make sure there is content between the brackets\n\treturn closeIdx > 0\n}\n\nfunc (ops HeaderOps) validate() error {\n\tfor fieldName, replacements := range ops.Replace {\n\t\tfor _, r := range replacements {\n\t\t\tif r.Search != \"\" && r.SearchRegexp != \"\" {\n\t\t\t\treturn fmt.Errorf(\"cannot specify both a substring search and a regular expression search for field '%s'\", fieldName)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// Replacement describes a string replacement,\n// either a simple and fast substring search\n// or a slower but more powerful regex search.\ntype Replacement struct {\n\t// The substring to search for.\n\tSearch string `json:\"search,omitempty\"`\n\n\t// The regular expression to search with.\n\tSearchRegexp string `json:\"search_regexp,omitempty\"`\n\n\t// The string with which to replace matches.\n\tReplace string `json:\"replace,omitempty\"`","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/headers/headers.go#L162-L198","documentation":"Returned by HeaderOps.validate (called during provisioning of header ops): a single replacement entry under header > replace declared both `search` (plain substring) and `search_regexp`. The replacement is either substring or regex, never both; declaring both is rejected so behavior stays unambiguous.","triggerScenarios":"A JSON config where a replacement object contains both \"search\" and \"search_regexp\" keys with non-empty values, e.g. {\"search\": \"foo\", \"search_regexp\": \"f(o+)\"}.","commonSituations":"Hand-editing JSON while migrating from substring to regex and leaving the old key; merging configs where both fields accumulate; Caddyfile users rarely hit this since its syntax produces one or the other.","solutions":["Delete one of the two keys — keep `search` for literal substring matching or `search_regexp` for regex.","If you need both behaviors, create two separate replacement entries in the field's array.","Run `caddy validate --adapter json --config caddy.json` to confirm."],"exampleFix":"// before\n\"replace\": { \"Cache-Control\": [ { \"search\": \"max-age\", \"search_regexp\": \"max-age=\\\\d+\" } ] }\n// after\n\"replace\": { \"Cache-Control\": [ { \"search_regexp\": \"max-age=\\\\d+\" } ] }","handlingStrategy":"type-guard","validationCode":"// JSON-level check: a replacement must set exactly one of search / search_regexp\nfor field, reps := range cfg.Replace {\n    for i, r := range reps {\n        if r.Search != \"\" && r.SearchRegexp != \"\" {\n            return fmt.Errorf(\"field %s replacement %d: set search or search_regexp, not both\", field, i)\n        }\n    }\n}","typeGuard":"// Structural guard for replacement objects (as a Go type constraint on your config loader)\ntype Replacement struct {\n    Search      string `json:\"search,omitempty\"`\n    SearchRegexp string `json:\"search_regexp,omitempty\"`\n}\nfunc (r Replacement) valid() bool { return (r.Search != \"\") != (r.SearchRegexp != \"\") }","tryCatchPattern":null,"preventionTips":["When migrating a replacement from substring to regex, delete the old key in the same commit.","Prefer the Caddyfile syntax (replace/a/replace_b) which cannot express both.","Lint JSON configs for replacement objects containing both keys."],"tags":["caddy","headers","replace","config-validation","json"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}