hashicorp/nomad · error

unexpected ExpirationLeeway type: %v

Error message

unexpected ExpirationLeeway type: %v

What it means

During JSON unmarshalling of ACLAuthMethod (custom UnmarshalJSON), ExpirationLeeway accepts only a string duration ("5m") or a JSON number (float64, treated as nanoseconds); any other JSON type (bool, object, array, null-like leftover) hits the default branch and returns this error.

Source

Thrown at nomad/structs/acl.go:1678

		*Alias
	}{
		Alias: (*Alias)(a),
	}
	if err = json.Unmarshal(data, &aux); err != nil {
		return err
	}
	if aux.ExpirationLeeway != nil {
		switch v := aux.ExpirationLeeway.(type) {
		case string:
			if v != "" {
				if a.ExpirationLeeway, err = time.ParseDuration(v); err != nil {
					return err
				}
			}
		case float64:
			a.ExpirationLeeway = time.Duration(v)
		default:
			return fmt.Errorf("unexpected ExpirationLeeway type: %v", v)
		}
	}
	if aux.NotBeforeLeeway != nil {
		switch v := aux.NotBeforeLeeway.(type) {
		case string:
			if v != "" {
				if a.NotBeforeLeeway, err = time.ParseDuration(v); err != nil {
					return err
				}
			}
		case float64:
			a.NotBeforeLeeway = time.Duration(v)
		default:
			return fmt.Errorf("unexpected NotBeforeLeeway type: %v", v)
		}
	}
	if aux.ClockSkewLeeway != nil {
		switch v := aux.ClockSkewLeeway.(type) {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Send ExpirationLeeway as a Go duration string like "5m" or "1h30m".
  2. Or send a bare JSON number interpreted as nanoseconds (300000000000 for 5m).
  3. Fix the client/SDK serialization to emit a scalar for this field.

Example fix

// before
{"ExpirationLeeway": {"minutes": 5}}
// after
{"ExpirationLeeway": "5m"}
Defensive patterns

Strategy: validation

Validate before calling

switch v := raw["ExpirationLeeway"].(type) {
case string, float64:
	// ok
default:
	return fmt.Errorf("ExpirationLeeway must be a duration string or number, got %T", v)
}

Type guard

func isDurationScalar(v interface{}) bool {
	switch v.(type) {
	case string, float64, float32, int64:
		return true
	}
	return false
}

Try / catch

if err := json.Unmarshal(payload, &am); err != nil {
	if strings.Contains(err.Error(), "unexpected ExpirationLeeway type") {
		return fmt.Errorf("send ExpirationLeeway as e.g. \"5m\" or a nanosecond number")
	}
	return err
}

Prevention

When it happens

Trigger: Posting auth-method JSON where ExpirationLeeway is e.g. true, an object {"seconds":300}, or an array — anything that is neither a string nor number.

Common situations: Hand-writing JSON config and quoting wrong / nesting durations; tools emitting YAML that maps to maps rather than scalar durations; forgetting Go's duration-string format and using "5 minutes".

Related errors


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