hashicorp/nomad · error

%s can't parse time duration %s

Error message

%s can't parse time duration %s

What it means

convertDurations converts HCL string fields (like gc_interval) into time.Duration values on the config struct. For fields with a direct pointer target, time.ParseDuration is called on the source string; this error reports the field name and the invalid value when parsing fails.

Source

Thrown at command/agent/config_parse.go:303

}

// durationConversionMap holds args for one duration conversion
type durationConversionMap struct {
	targetFieldPath string
	targetField     *time.Duration
	sourceField     *string
	setFunc         func(*time.Duration)
}

// convertDurations parses the duration strings specified in the config files
// into time.Durations
func convertDurations(xs []durationConversionMap) error {
	for _, x := range xs {
		// if targetField is not a pointer itself, use the field map.
		if x.targetField != nil && x.sourceField != nil && "" != *x.sourceField {
			d, err := time.ParseDuration(*x.sourceField)
			if err != nil {
				return fmt.Errorf("%s can't parse time duration %s", x.targetFieldPath, *x.sourceField)
			}

			*x.targetField = d
		} else if x.setFunc != nil && x.sourceField != nil && "" != *x.sourceField {
			// if targetField is a pointer itself, use the setFunc closure.
			d, err := time.ParseDuration(*x.sourceField)
			if err != nil {
				return fmt.Errorf("%s can't parse time duration %s", x.targetFieldPath, *x.sourceField)
			}
			x.setFunc(&d)
		}
	}

	return nil
}

func extraKeys(c *Config) error {
	// hcl leaves behind extra keys when parsing JSON. These keys

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Add a valid Go duration unit: `gc_interval = "30s"`, `"5m"`, `"1h"`, etc.
  2. Use exactly the units Go supports: ns, us, ms, s, m, h (not min/hr/d)
  3. Check the field path named in the error message and fix that specific value
  4. Quote the value in HCL so it is parsed as a string with units, not a number

Example fix

// before
gc_interval = 30

// after
gc_interval = "30s"
Defensive patterns

Strategy: validation

Validate before calling

var durationRe = regexp.MustCompile(`^-?\d+(\.\d+)?(ns|us|µs|ms|s|m|h)$`)
func validateDurationFields(cfg map[string]any, keys ...string) error {
	for _, k := range keys {
		v, ok := cfg[k].(string)
		if !ok || !durationRe.MatchString(v) {
			return fmt.Errorf("%s = %v is not a valid Go duration", k, cfg[k])
		}
	}
	return nil
}

Type guard

func isParsableDuration(s string) bool {
	_, err := time.ParseDuration(s)
	return err == nil
}

Try / catch

cfg, err := ParseConfigFile(path)
if err != nil {
	if strings.Contains(err.Error(), "can't parse time duration") {
		return fmt.Errorf("fix duration value in %s (units: ns/us/ms/s/m/h): %w", path, err)
	}
	return err
}

Prevention

When it happens

Trigger: ParseConfigFile on a config where a duration field (e.g. gc_interval, or any entry in the durationConversionMap list) is set to a string not accepted by time.ParseDuration — e.g. "5", "five minutes", "1hr", or an empty-ish/whitespace value that still passes the != "" check.

Common situations: Writing `gc_interval = 30` (a bare number, no unit) instead of `gc_interval = "30s"`; using units Go doesn't understand like "min" or "hr"; values from templates rendering without units.

Understand the failure class

Background: "invalid duration" / "failed to parse duration": why your timeout, interval, or TTL string is rejected and which formats each library accepts — this error's family across 32 libraries.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/2fbcb88f2bc18c27. Report an issue: GitHub.