gohugoio/hugo · critical

failed to load config

Error message

failed to load config: %v

What it means

The panic-recovery guard at the top of LoadConfig. A deferred recover() catches any panic during the entire config-loading pipeline and converts it into an error using %v (note: not %w, so the original panic value is stringified, not unwrappable). It also prints the stack trace via debug.PrintStack. This is the catch-all that prevents a raw panic from crashing the process.

Solutions

  1. Read the printed stack trace to find the panicking file:line and the triggering value.
  2. If the panic is `unknown type in languages config`, a language entry has a non-Params/non-merge-strategy type; normalize it.
  3. Reduce config to a minimal repro and report the issue with the stack trace if it looks like an internal bug.
  4. Update Hugo to the latest patch release in case the panic is a known fixed defect.

Example fix

# before: a language value is a string instead of a table
[languages]
fr = "french"

# after
[languages.fr]
weight = 20
Defensive patterns

Strategy: try-catch

Try / catch

// Wrap LoadConfig in error handling; panics are already converted to errors
// by Hugo's recover(), so a normal err check suffices.
cfgs, err := allconfig.LoadConfig(d)
if err != nil {
    // err.Error() begins with "failed to load config: <panic value>"
    // and the stack trace has already been printed to stderr.
    return err
}

Prevention

When it happens

Trigger: A nil-pointer dereference or index-out-of-range inside any decoder or helper during config load; an internal assertion failure (e.g. the `panic(fmt.Sprintf("unknown type in languages config: %T", v))` in allconfig.go:1161); a bug in a third-party module hook executed during load.

Common situations: Hitting an internal Hugo bug on an edge-case config; a module/theme whose config triggers an unhandled code path; feeding config values of unexpected Go types (e.g. via mapstructure quirks).

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/7c898951b34eb9c6. Report an issue: GitHub.

Appendix: source

Thrown at config/allconfig/load.go:48

	"github.com/gohugoio/hugo/common/loggers"
	"github.com/gohugoio/hugo/common/paths"
	"github.com/gohugoio/hugo/common/types"
	"github.com/gohugoio/hugo/config"
	"github.com/gohugoio/hugo/helpers"
	hglob "github.com/gohugoio/hugo/hugofs/hglob"
	"github.com/gohugoio/hugo/modules"
	"github.com/gohugoio/hugo/modules/npm"
	"github.com/gohugoio/hugo/parser/metadecoders"
	"github.com/spf13/afero"
)

//lint:ignore ST1005 end user message.
var ErrNoConfigFile = errors.New("Unable to locate config file or config directory. Perhaps you need to create a new project.\n       Run `hugo help new` for details.\n")

func LoadConfig(d ConfigSourceDescriptor) (configs *Configs, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("failed to load config: %v", r)
			debug.PrintStack()
		}
	}()

	if len(d.Environ) == 0 && !hugo.IsRunningAsTest() {
		d.Environ = os.Environ()
	}

	if d.Logger == nil {
		d.Logger = loggers.NewDefault()
	}

	l := &configLoader{ConfigSourceDescriptor: d, cfg: config.New()}
	// Make sure we always do this, even in error situations,
	// as we have commands (e.g. "hugo mod init") that will
	// use a partial configuration to do its job.
	defer l.deleteMergeStrategies()
	res, _, err := l.loadConfigMain(d)

View on GitHub (pinned to 52c9bd7908)