{"record":{"id":"e011dde817c71042","repo":"gohugoio/hugo","slug":"invalid-cache-config-s","errorCode":null,"errorMessage":"invalid cache config: %s","messagePattern":"invalid cache config: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"cache/filecache/filecache.go","lineNumber":81,"sourceCode":"}\n\n// Lock tracks the ids in use. We use this information to do garbage collection\n// after a Hugo build.\nfunc (l *lockTracker) Lock(id string) {\n\tl.seen.AddIfAbsent(id)\n\tl.Locker.Lock(id)\n}\n\n// ItemInfo contains info about a cached file.\ntype ItemInfo struct {\n\t// This is the file's name relative to the cache's filesystem.\n\tName string\n}\n\n// NewCache creates a new file cache with the given filesystem and max age.\nfunc NewCache(fs afero.Fs, cfg FileCacheConfig) *Cache {\n\tif err := cfg.init(); err != nil {\n\t\tpanic(fmt.Sprintf(\"invalid cache config: %s\", err))\n\t}\n\n\treturn &Cache{\n\t\tFs:          fs,\n\t\tentryLocker: &lockTracker{Locker: locker.NewLocker(), seen: maphelpers.NewConcurrentSet[string]()},\n\t\tcfg:         cfg,\n\t}\n}\n\n// lockedFile is a file with a lock that is released on Close.\ntype lockedFile struct {\n\tafero.File\n\tunlock func()\n}\n\nfunc (l *lockedFile) Close() error {\n\tdefer l.unlock()\n\treturn l.File.Close()","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/gohugoio/hugo/blob/52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2/cache/filecache/filecache.go#L63-L99","documentation":"A panic raised in NewCache (cache/filecache/filecache.go:81) when FileCacheConfig.init() returns an error. Currently init() only errors when the Dir still contains ':' placeholders that were not resolved during DecodeConfig. Hugo panics because a half-resolved cache directory would silently corrupt builds.","triggerScenarios":"Programmatically building a FileCacheConfig with a Dir containing an unresolved ':' placeholder and calling NewCache directly, bypassing DecodeConfig's placeholder resolution.","commonSituations":"Custom test setups that construct FileCacheConfig by hand; an unknown placeholder (not :cacheDir/:resourceDir/:project) slipping through resolution.","solutions":["Always run cache configs through filecache.DecodeConfig so placeholders are resolved before NewCache","Avoid unknown ':' placeholders in cache dir configuration","In tests, set DirCompiled directly rather than Dir with placeholders"],"exampleFix":"// before\nc := NewCache(fs, FileCacheConfig{Dir: \":cacheDir/modules\"})\n// after\ncfgs, _ := filecache.DecodeConfig(fs, bcfg, map[string]any{})\nc := NewCache(fs, cfgs[filecache.CacheKeyModules])","handlingStrategy":"validation","validationCode":"// Resolve placeholders via DecodeConfig before constructing a cache.\nif err := cfg.init(); err != nil {\n    log.Fatal(err)\n}\n// Or always go through:\n// cfgs, err := filecache.DecodeConfig(fs, bcfg, userConfig)","typeGuard":null,"tryCatchPattern":"// Panics cannot be caught cleanly in production; prefer validation.\n// If unavoidable, isolate in a goroutine with recover:\nfunc safeNewCache(fs afero.Fs, cfg FileCacheConfig) (c *Cache, err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"NewCache panic: %v\", r)\n        }\n    }()\n    return NewCache(fs, cfg), nil\n}","preventionTips":["Always construct caches through filecache.DecodeConfig","Avoid ':' placeholders other than :cacheDir/:resourceDir/:project","In tests set DirCompiled directly instead of Dir with placeholders"],"tags":["cache","config","panic","filesystem"],"backgroundTag":null,"analyzedSha":"52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2","analyzedAt":"2026-08-09T21:49:36.660Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}