gohugoio/hugo · error

cache dir %q contains unresolved placeholders

Error message

cache dir %q contains unresolved placeholders

What it means

Raised by FileCacheConfig.init in Hugo's filecache package when the compiled cache directory (DirCompiled) is empty and the raw Dir string still contains a ':' character, indicating placeholder tokens like :cacheDir or :resourceDir were never resolved by DecodeConfig. Hugo refuses to use such a path because writing cache files to a literal ':cacheDir/...' path would create bogus directories. This path is mostly hit when a FileCacheConfig is constructed programmatically (e.g. in unit tests) and init() runs without the normal DecodeConfig placeholder-resolution pass.

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 8a468df065)

Solutions

  1. Ensure cache configs go through filecache.DecodeConfig so :cacheDir/:resourceDir placeholders are resolved into DirCompiled before init() runs.
  2. In tests, set Dir to a concrete absolute path (no ':' placeholders) or set DirCompiled explicitly.
  3. Check caches.*.dir in your site config for typos like a stray colon inside the path.

Example fix

// before (test code)
cfg := filecache.FileCacheConfig{Dir: ":cacheDir/images"}
cfg.init() // error
// after
cfg := filecache.FileCacheConfig{Dir: "/tmp/hugo_cache/images"}
cfg.init()
Defensive patterns

Strategy: validation

Validate before calling

// Before building config, verify resolved dir has no leftover placeholders
if strings.Contains(resolvedDir, ":") {
    return fmt.Errorf("cache dir %q still contains placeholders", resolvedDir)
}

Prevention

When it happens

Trigger: Calling FileCacheConfig.init() with DirCompiled unset and Dir containing ':' — e.g. constructing filecache configs directly in tests or embedding code without running filecache.DecodeConfig first; or a config path where placeholder substitution was skipped so Dir is still ':cacheDir/images'.

Common situations: Unit tests or tools that build Hugo config objects manually instead of going through the full config decode; custom integrations of the filecache package; a Windows path with a drive-letter colon in an unexpected position combined with a code path that bypasses placeholder resolution.

Related errors


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/b7a691c1f0476058.json. Report an issue: GitHub ↗.