derailed/k9s · error

main config.yaml load failed: %w

Error message

main config.yaml load failed: %w

What it means

Returned from Config.Load (internal/config/config.go:288) when yaml.Unmarshal of the main config file into a Config struct fails. Unlike the schema check (which only warns into the joined error), a structural unmarshal failure means the file cannot be mapped onto the config types: wrong value types (string where int expected), duplicate keys, or malformed YAML. Joined with other errors and returned after the Merge of the zero-value/previous config.

Source

Thrown at internal/config/config.go:288

// Load loads K9s configuration from file.
func (c *Config) Load(path string, force bool) error {
	if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
		if err := c.Save(force); err != nil {
			return err
		}
	}
	bb, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	var errs error
	if err := data.JSONValidator.Validate(json.K9sSchema, bb); err != nil {
		errs = errors.Join(errs, fmt.Errorf("k9s config file %q load failed:\n%w", path, err))
	}

	var cfg Config
	if err := yaml.Unmarshal(bb, &cfg); err != nil {
		errs = errors.Join(errs, fmt.Errorf("main config.yaml load failed: %w", err))
	}
	c.Merge(&cfg)

	return errs
}

// Save configuration to disk.
func (c *Config) Save(force bool) error {
	contextName := c.K9s.ActiveContextName()
	// Skip saving if no context is configured
	if contextName == "" {
		slog.Debug("No context configured, skipping config save")
		return nil
	}
	clusterName, err := c.ActiveClusterName(contextName)
	if err != nil {
		return fmt.Errorf("unable to locate associated cluster for context %q: %w", contextName, err)
	}

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Run yamllint (or python -c 'import yaml,sys; yaml.safe_load(open("config.yml"))') to get the exact line/column
  2. Fix the reported structural problem — usually indentation or type
  3. Restore from backup or delete the file so k9s regenerates defaults, then re-apply changes incrementally

Example fix

# before
k9s:
    refreshRate: fast
    headless: 2

# after
k9s:
  refreshRate: 2
  headless: false
Defensive patterns

Strategy: try-catch

Validate before calling

func configParses(path string) error {
	bb, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	var probe map[string]any
	if err := yaml.Unmarshal(bb, &probe); err != nil {
		return fmt.Errorf("config.yml invalid YAML: %w", err)
	}
	return nil
}

Type guard

func isYamlTypeError(err error) bool {
	var te *yaml.TypeError
	return errors.As(err, &te)
}

Try / catch

if err := cfg.Load(path, false); err != nil {
	var te *yaml.TypeError
	if errors.As(err, &te) {
		for _, msg := range te.Errors {
			log.Printf("config type error: %s", msg) // line + expected type
		}
		// offer to regenerate the file from defaults
	}
}

Prevention

When it happens

Trigger: Hand-editing config.yml and introducing a tab character, wrong indentation level, or a scalar of the wrong type (e.g. refreshRate: fast instead of an integer); YAML aliases/anchors the decoder rejects; file truncated mid-write.

Common situations: Manual edits late at night; editor inserting tabs; syncing dotfiles that got mangled by templating; disk-full partial writes.

Related errors


AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15). Data as JSON: /api/errors/cdb16dfb93c07ebc. Report an issue: GitHub.