{"record":{"id":"a52d9d52c5927b35","repo":"gohugoio/hugo","slug":"failed-to-compile-cache-buster-target-q-w","errorCode":null,"errorMessage":"failed to compile cache buster target %q: %w","messagePattern":"failed to compile cache buster target %q: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"config/commonConfig.go","lineNumber":436,"sourceCode":"\t\tm := sourceRe.FindStringSubmatch(s)\n\t\tmatchString := \"no match\"\n\t\tmatch := m != nil\n\t\tif match {\n\t\t\tmatchString = \"match!\"\n\t\t}\n\t\tdebugl.Logf(\"Matching %q with source %q: %s\", s, source, matchString)\n\t\tif !match {\n\t\t\treturn nil\n\t\t}\n\t\tgroups := m[1:]\n\t\tcurrentTarget := target\n\t\t// Replace $1, $2 etc. in target.\n\t\tfor i, g := range groups {\n\t\t\tcurrentTarget = strings.ReplaceAll(target, fmt.Sprintf(\"$%d\", i+1), g)\n\t\t}\n\t\ttargetRe, err := regexp.Compile(currentTarget)\n\t\tif err != nil {\n\t\t\tcompileErr = fmt.Errorf(\"failed to compile cache buster target %q: %w\", currentTarget, err)\n\t\t\treturn nil\n\t\t}\n\t\treturn func(ss string) bool {\n\t\t\tmatch := targetRe.MatchString(ss)\n\t\t\tmatchString := \"no match\"\n\t\t\tif match {\n\t\t\t\tmatchString = \"match!\"\n\t\t\t}\n\t\t\tlogger.Debugf(\"Matching %q with target %q: %s\", ss, currentTarget, matchString)\n\n\t\t\treturn match\n\t\t}\n\t}\n\treturn compileErr\n}\n\nfunc (r Redirect) IsZero() bool {\n\treturn r.From == \"\" && r.FromRe == \"\"","sourceCodeStart":418,"sourceCodeEnd":454,"githubUrl":"https://github.com/gohugoio/hugo/blob/52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2/config/commonConfig.go#L418-L454","documentation":"Hugo's cache-buster config (CacheBuster.CompileConfig in config/commonConfig.go) compiles a Source regexp, then on each match substitutes Source capture groups ($1, $2, ...) into the Target string and recompiles that substituted string as a regexp. This error fires when the resulting Target string is not valid Go regexp syntax (regexp.Compile at line 434 fails). It is a runtime/compile error surfaced during Hugo's config compile phase, not at file load time.","triggerScenarios":"Configuring a cacheBuster where Source captures content containing regexp metacharacters that, once substituted into Target, break the regex (e.g. Target = \"$1.css\" and $1 expands to \"file(name\"); or hardcoding a Target value like \"(css|.css\" that is itself invalid. The compile is deferred until the closure actually runs against a matched asset path.","commonSituations":"Author sets cacheBuster.Target expecting literal matching but writes regex syntax; capture groups from Source inject unescaped user/path content into Target; mismatched number of $N references versus Source capture groups producing dangling references.","solutions":["Test the Target string in isolation with regexp.Compile in a scratch program before putting it in config.","Avoid injecting captured group content ($1) into Target unless you are certain it cannot contain regexp metacharacters; escape it or match it literally.","Ensure the number of capturing groups in Source matches the highest $N referenced in Target.","If literal matching is intended, anchor and escape the Target (e.g. use \\. for a literal dot)."],"exampleFix":"// before (config)\n[cacheBuster]\n  source = \"(\\\\.(css|js))$\"\n  target = \"$1?v=\"  // $1 expands to '.css' -> '.css?v=' which is valid, but if source\n                 // captures user data this breaks; prefer a static target\n\n// after\n[cacheBuster]\n  source = \"\\\\.(css|js)$\"\n  target = \"\\\\.(css|js)$\"  // static, valid regex, no group injection","handlingStrategy":"validation","validationCode":"// Validate a cacheBuster Source/Target pair before applying it.\nimport \"regexp\"\n\nfunc ValidateCacheBuster(source, target string, sampleMatches ...string) error {\n\tsrcRe, err := regexp.Compile(source)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"source: %w\", err)\n\t}\n\tfor _, s := range sampleMatches {\n\t\tm := srcRe.FindStringSubmatch(s)\n\t\tif m == nil {\n\t\t\tcontinue\n\t\t}\n\t\tcur := target\n\t\tfor i, g := range m[1:] {\n\t\t\tcur = strings.ReplaceAll(cur, fmt.Sprintf(\"$%d\", i+1), g)\n\t\t}\n\t\tif _, err := regexp.Compile(cur); err != nil {\n\t\t\treturn fmt.Errorf(\"target after substitution %q: %w\", cur, err)\n\t\t}\n\t}\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":"// CacheBuster.CompileConfig returns the error from config compilation;\n// surface it at config-load time rather than at build time.\nif err := cacheBuster.CompileConfig(logger); err != nil {\n    return fmt.Errorf(\"config: %w\", err)\n}","preventionTips":["Never inject untrusted/captured text into the Target regex; keep Target static when possible.","Keep the number of $N references in Target <= the number of capture groups in Source.","Unit-test the Source/Target pair against representative asset filenames."],"tags":["config","regex","assets","caching"],"backgroundTag":null,"analyzedSha":"52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2","analyzedAt":"2026-08-09T21:49:36.660Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}