hashicorp/nomad · error
unexpected ExpirationLeeway type: %v
Error message
unexpected ExpirationLeeway type: %v
What it means
In api/acl.go, when unmarshaling a bound ACL (AuthMethod/BindingRule clock skew config), the JSON field for ExpirationLeeway may arrive as a string or number. If the raw JSON value is neither string nor float64, this error is returned during custom UnmarshalJSON handling of the leeway duration.
Source
Thrown at api/acl.go:956
}{
Alias: (*Alias)(c),
}
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
var err error
if aux.ExpirationLeeway != nil {
switch v := aux.ExpirationLeeway.(type) {
case string:
if v != "" {
if c.ExpirationLeeway, err = time.ParseDuration(v); err != nil {
return err
}
}
case float64:
c.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 c.NotBeforeLeeway, err = time.ParseDuration(v); err != nil {
return err
}
}
case float64:
c.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
- Send ExpirationLeeway as a duration string like "5m", "1h30m", or as an integer number of nanoseconds.
- Fix client code so it marshals time.Duration values as strings/ints rather than objects.
- Confirm the API version supports the field and the struct's custom UnmarshalJSON is invoked (not plain json.Unmarshal into a conflicting type).
Example fix
// before
{"ExpirationLeeway": {"value": 5, "unit": "m"}}
// after
{"ExpirationLeeway": "5m"} Defensive patterns
Strategy: validation
Validate before calling
// ensure leeway fields are string durations before sending
func validLeeway(v interface{}) bool {
switch t := v.(type) {
case string:
_, err := time.ParseDuration(t)
return err == nil
case float64:
return true
default:
return false
}
} Type guard
func asDurationString(v interface{}) (string, bool) {
s, ok := v.(string)
if !ok {
return "", false
}
if _, err := time.ParseDuration(s); err != nil {
return "", false
}
return s, true
} Prevention
- Always serialize time.Duration fields as strings in client payloads.
- Validate request JSON against the auth-method schema before POST/PUT.
- Avoid re-encoding API responses through intermediate structs that change field types.
When it happens
Trigger: POST/PUT to /v1/acl/auth-method (or decoding an auth method response) where the `ExpirationLeeway` field in the JSON payload is an object, bool, null-as-non-pointer, or otherwise not a string duration or numeric nanoseconds.
Common situations: Hand-built JSON payloads sending `{"ExpirationLeeway": "3 hours and 5 minutes"}` or nested objects; SDK consumers marshaling a struct into the aux field with the wrong type; Go time.Duration marshaled as a struct.
Related errors
- unexpected NotBeforeLeeway type: %v
- unexpected ClockSkewLeeway type: %v
- unexpected ExpirationLeeway type: %v
- unexpected NotBeforeLeeway type: %v
- default auth config text could not be deserialized: %v
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/b7714519b2489de6.
Report an issue: GitHub.