gohugoio/hugo · error
cache dir contains unresolved placeholders
Error message
cache dir %q contains unresolved placeholders
What it means
In FileCacheConfig.init (filecache_config.go:123-128), when DirCompiled is empty but the raw Dir string contains a colon, it means a placeholder like :cacheDir was never resolved. This guard is primarily reached from unit tests that construct FileCacheConfig directly without running the full DecodeConfig pipeline.
Solutions
- Run the config through DecodeConfig to resolve placeholders before use
- Set DirCompiled explicitly if intentionally bypassing the decode pipeline
- Avoid colons in raw directory names used in test fixtures
Example fix
// before
cfg := FileCacheConfig{Dir: ":cacheDir/modules"}
cfg.init() // error: unresolved placeholder
// after
cfg.DirCompiled = "/tmp/hugo-cache/modules"
cfg.init() Defensive patterns
Strategy: validation
Validate before calling
// In test code, resolve placeholders or set DirCompiled before calling init.
func mustResolveDir(t *testing.T, cfg *FileCacheConfig, baseCacheDir string) {
t.Helper()
if cfg.DirCompiled == "" && strings.Contains(cfg.Dir, ":") {
cfg.DirCompiled = filepath.Join(baseCacheDir, strings.TrimPrefix(cfg.Dir, ":cacheDir"))
}
} Prevention
- In tests, always run config through DecodeConfig rather than constructing FileCacheConfig directly
- Set DirCompiled explicitly when bypassing the decode pipeline
- Avoid colon characters in literal test directory names
When it happens
Trigger: Constructing a FileCacheConfig in Go code/tests with Dir set to ":cacheDir/foo" but never calling DecodeConfig to resolve placeholders into DirCompiled.
Common situations: Unit tests of the filecache package that bypass config decoding; custom code that instantiates FileCacheConfig directly.
Related errors
- is not a valid placeholder (valid values are :cacheDir or…
- failed to decode filecache config
- is a root folder and not allowed as cache dir
- is not a valid cache name
- must resolve to an absolute directory
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/b7a691c1f0476058.
Report an issue: GitHub.
Appendix: source
Thrown at cache/filecache/filecache_config.go:127
// be removed and not returned from the cache.
// A negative value means forever, 0 means cache is disabled.
// Hugo is lenient with what types it accepts here, but we recommend using
// a duration string, a sequence of decimal numbers, each with optional fraction and a unit suffix,
// such as "300ms", "1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
MaxAge time.Duration
// The directory where files are stored.
Dir string
fileCacheConfigInternal `json:"-"`
}
func (cfg *FileCacheConfig) init() error {
if cfg.DirCompiled == "" {
// From unit tests. Just check that it does not contain any placeholders.
if strings.Contains(cfg.Dir, ":") {
return fmt.Errorf("cache dir %q contains unresolved placeholders", cfg.Dir)
}
cfg.DirCompiled = cfg.Dir
}
// Sanity check the config.
if len(cfg.DirCompiled) < 5 {
panic(fmt.Sprintf("invalid cache dir: %q", cfg.DirCompiled))
}
return nil
}
type fileCacheConfigInternal struct {
DirCompiled string
name string // The name of this cache, e.g. "images", "modules" etc.
entryIsDir bool // when set, the cache entries represents directories directly below the base dir.
isReadOnly bool // when set, the cache is read only and needs to be pruned differently. This is used for the Go modules cache.
IsResourceDir bool // resources/_gen will get its own composite filesystem that also checks any theme. TODO(bep) unexport this.
}View on GitHub (pinned to 52c9bd7908)