air-verse/air · error

failed to parse %s: %w

Error message

failed to parse %s: %w

What it means

Air wraps the underlying TOML decode failure when parsing a user-supplied config file. If the config file exists but cannot be parsed (invalid TOML syntax, wrong types), air returns this wrapped error instead of silently falling back to defaults. Defaults are only used when no config file exists.

Source

Thrown at runner/config.go:390

		return "", fmt.Errorf("failed to write to %s: %w", dftTOML, err)
	}

	return dftTOML, nil
}

// defaultPathConfig loads `.air.toml` or `.config/air.toml` when present.
// The bool is true when a config file was loaded, false when defaults are returned.
func defaultPathConfig() (*Config, bool, error) {
	// when path is blank, first find `.air.toml` in `air_wd` and current working directory or .config/air.toml, if not found, use defaults
	for _, name := range []string{dftTOML, cfgTOML} {
		cfg, err := readConfByName(name)
		if err == nil {
			return cfg, true, nil
		}
		// If the config file exists but failed to parse, report the error
		// Only use defaults if no config file exists
		if !os.IsNotExist(err) {
			return nil, false, fmt.Errorf("failed to parse %s: %w", name, err)
		}
	}

	dftCfg := defaultConfig()
	return &dftCfg, false, nil
}

func readConfByName(name string) (*Config, error) {
	var path string
	if wd := os.Getenv(airWd); wd != "" {
		path = filepath.Join(wd, name)
	} else {
		wd, err := os.Getwd()
		if err != nil {
			return nil, err
		}
		path = filepath.Join(wd, name)
	}

View on GitHub (pinned to 71ea1dee05)

Solutions

  1. Read the wrapped `%w` cause to find the exact TOML line/column and fix the syntax
  2. Validate the file with a TOML linter (e.g. `tomlcheck .air.toml` or taplo)
  3. If unsure, regenerate a known-good config with `air init` and re-apply your customizations
  4. Ensure the file extension matches its content (.toml for TOML)

Example fix

// before (.air.toml)
[build]
bin = "tmp/main  // unterminated string
// after
[build]
bin = "tmp/main"
Defensive patterns

Strategy: validation

Validate before calling

// before running air
func configValid(path string) error {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		return nil // air will use defaults
	}
	var cfg map[string]any
	_, err := toml.DecodeFile(path, &cfg)
	return err
}

Try / catch

if err := runAir(); err != nil {
	var perr *toml.ParseError
	if errors.As(err, &perr) {
		log.Fatalf("fix config syntax: %v", perr) // points at line/col
	}
	log.Fatal(err)
}

Prevention

When it happens

Trigger: Running `air` (or air's config-loading path via parseConfig) with a .air.toml/.air.conf file that exists but contains malformed TOML or a field with an incompatible type; parseFile decodes it with the toml library and the decode error is wrapped here.

Common situations: Hand-edited config with a missing quote, unbalanced bracket, or tab/indentation mistake; copying a config from an older air version with renamed/retyped fields; accidentally writing JSON or YAML into a .toml file.

Understand the failure class

Related errors


AI-assisted analysis of air-verse/air@71ea1dee05 (2026-08-31). Data as JSON: /api/errors/4c3439947f603633. Report an issue: GitHub.