JanDeDobbeleer/oh-my-posh · error

failed to parse theme %s

Error message

failed to parse theme %s

What it means

recordThemeSanitized (used by `config export data --themes --sanitize`) loads a theme file with config.Load. If the resulting config has an empty Source — the loader failed to read or parse the file — it returns "failed to parse theme <path>". It means the theme file at that path could not be loaded as a valid oh-my-posh config.

Source

Thrown at src/cli/config_export_data.go:214

	dataCmd.Flags().StringVar(&dataPath, "data", "",
		"path to a template data file to seed the recording with, instead of the live environment")
	dataCmd.Flags().StringVar(&themesDir, "themes", "",
		"record every theme in this directory and merge them into one sanitized fixture, ignoring --config (requires --sanitize)")

	exportCmd.AddCommand(dataCmd)
}

// recordThemeSanitized runs one theme's segments against the real environment,
// exactly as the single-config path above does, then returns its sanitized env
// and segment maps for merging. Each call gets its own fresh template cache
// (resetTemplateCache=true, below, drives render.Config's own
// template.ResetCache before template.Init) so one theme's Var/Maps never leak
// into the next theme's render, the same isolation prompt/golden_test.go's
// renderTheme relies on between themes.
func recordThemeSanitized(themePath string) (env, segments map[string]json.RawMessage, err error) {
	cfg := config.Load(themePath)
	if cfg.Source == "" {
		return nil, nil, fmt.Errorf("failed to parse theme %s", themePath)
	}

	// --data seeds the writers with a fixture's own values before they render, so re-recording an
	// existing file keeps what it was curated with and only adds what the format has since gained.
	// Without it every theme would record whatever this machine happens to look like.
	if _, err := render.Config(cfg, 120, true, func(flags *runtime.Flags) error {
		return applyDataFile(flags, func(string) bool { return false })
	}); err != nil {
		return nil, nil, fmt.Errorf("failed to record theme %s: %w", themePath, err)
	}

	doc, err := buildDataDocument(cfg)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to record theme %s: %w", themePath, err)
	}

	sanitized, err := sanitizeDataDocument(doc, cfg)
	if err != nil {

View on GitHub (pinned to 0976794618)

Solutions

  1. Open the named theme file and fix or remove it from the directory
  2. Validate the file parses (run `oh-my-posh config migrate --config <file>` or print with it) to see the underlying syntax error
  3. Check file permissions and that the file is not empty
  4. Point --themes at a directory containing only valid theme configs (e.g. the bundled themes/ folder)

Example fix

// before
--themes ../my-mixed-dir   # contains theme.json.bak

// after
--themes ../themes          # only valid *.omp.json files
Defensive patterns

Strategy: validation

Validate before calling

// Go: verify the theme loads before batch-recording
var cfg = config.Load(path)
if cfg.Source == "" {
	return fmt.Errorf("theme %s is not a valid config", path)
}

Type guard

func themeLoads(path string) bool {
	return config.Load(path).Source != ""
}

Try / catch

env, segments, err := recordThemeSanitized(themePath)
if err != nil {
	if strings.Contains(err.Error(), "failed to parse theme") {
		fmt.Fprintf(os.Stderr, "skipping invalid theme: %v\n", err)
		return nil // or fail fast, per policy
	}
	return err
}

Prevention

When it happens

Trigger: Running the --themes merge over a directory where one of the discovered files is unreadable, empty, or not a valid TOML/JSON/YAML config, so config.Load yields cfg.Source == "".

Common situations: Pointing --themes at a directory containing stray non-config files (README, partial JSON, editor backups like theme.json~); a truncated download; wrong file extension; permission errors on the file.

Understand the failure class

Related errors


AI-assisted analysis of JanDeDobbeleer/oh-my-posh@0976794618 (2026-08-31). Data as JSON: /api/errors/f1e73b26982794b2. Report an issue: GitHub.