gohugoio/hugo · error
failed to compile cache buster target
Error message
failed to compile cache buster target %q: %w
What it means
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.
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).
Example fix
// before (config)
[cacheBuster]
source = "(\\.(css|js))$"
target = "$1?v=" // $1 expands to '.css' -> '.css?v=' which is valid, but if source
// captures user data this breaks; prefer a static target
// after
[cacheBuster]
source = "\\.(css|js)$"
target = "\\.(css|js)$" // static, valid regex, no group injection Defensive patterns
Strategy: validation
Validate before calling
// Validate a cacheBuster Source/Target pair before applying it.
import "regexp"
func ValidateCacheBuster(source, target string, sampleMatches ...string) error {
srcRe, err := regexp.Compile(source)
if err != nil {
return fmt.Errorf("source: %w", err)
}
for _, s := range sampleMatches {
m := srcRe.FindStringSubmatch(s)
if m == nil {
continue
}
cur := target
for i, g := range m[1:] {
cur = strings.ReplaceAll(cur, fmt.Sprintf("$%d", i+1), g)
}
if _, err := regexp.Compile(cur); err != nil {
return fmt.Errorf("target after substitution %q: %w", cur, err)
}
}
return nil
} Try / catch
// CacheBuster.CompileConfig returns the error from config compilation;
// surface it at config-load time rather than at build time.
if err := cacheBuster.CompileConfig(logger); err != nil {
return fmt.Errorf("config: %w", err)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to compile ignoreFiles pattern
- failed to compile whitelist pattern
- inject: absolute paths not supported, must be relative to…
- inject: file not found
- invalid deployment.matchers.pattern
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/a52d9d52c5927b35.
Report an issue: GitHub.
Appendix: source
Thrown at config/commonConfig.go:436
m := sourceRe.FindStringSubmatch(s)
matchString := "no match"
match := m != nil
if match {
matchString = "match!"
}
debugl.Logf("Matching %q with source %q: %s", s, source, matchString)
if !match {
return nil
}
groups := m[1:]
currentTarget := target
// Replace $1, $2 etc. in target.
for i, g := range groups {
currentTarget = strings.ReplaceAll(target, fmt.Sprintf("$%d", i+1), g)
}
targetRe, err := regexp.Compile(currentTarget)
if err != nil {
compileErr = fmt.Errorf("failed to compile cache buster target %q: %w", currentTarget, err)
return nil
}
return func(ss string) bool {
match := targetRe.MatchString(ss)
matchString := "no match"
if match {
matchString = "match!"
}
logger.Debugf("Matching %q with target %q: %s", ss, currentTarget, matchString)
return match
}
}
return compileErr
}
func (r Redirect) IsZero() bool {
return r.From == "" && r.FromRe == ""View on GitHub (pinned to 52c9bd7908)