{"record":{"id":"d1d781d217a75f5e","repo":"hashicorp/nomad","slug":"unexpected-notbeforeleeway-type-v-d1d781","errorCode":null,"errorMessage":"unexpected NotBeforeLeeway type: %v","messagePattern":"unexpected NotBeforeLeeway type: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/acl.go","lineNumber":1692,"sourceCode":"\t\t\t}\n\t\tcase float64:\n\t\t\ta.ExpirationLeeway = time.Duration(v)\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unexpected ExpirationLeeway type: %v\", v)\n\t\t}\n\t}\n\tif aux.NotBeforeLeeway != nil {\n\t\tswitch v := aux.NotBeforeLeeway.(type) {\n\t\tcase string:\n\t\t\tif v != \"\" {\n\t\t\t\tif a.NotBeforeLeeway, err = time.ParseDuration(v); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\tcase float64:\n\t\t\ta.NotBeforeLeeway = time.Duration(v)\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unexpected NotBeforeLeeway type: %v\", v)\n\t\t}\n\t}\n\tif aux.ClockSkewLeeway != nil {\n\t\tswitch v := aux.ClockSkewLeeway.(type) {\n\t\tcase string:\n\t\t\tif v != \"\" {\n\t\t\t\tif a.ClockSkewLeeway, err = time.ParseDuration(v); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\tcase float64:\n\t\t\ta.ClockSkewLeeway = time.Duration(v)\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unexpected ClockSkewLeeway type: %v\", v)\n\t\t}\n\t}\n\treturn nil\n}","sourceCodeStart":1674,"sourceCodeEnd":1710,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/acl.go#L1674-L1710","documentation":"While decoding an ACL auth method's leeway settings from JSON, NotBeforeLeeway had an unexpected type (neither string nor number); the type switch in UnmarshalJSON rejects values it cannot convert to a duration.","triggerScenarios":"Submitting auth-method JSON where NotBeforeLeeway is a boolean, nested object, or array instead of a string duration or number.","commonSituations":"Copy-paste typos in the JWT/OIDC config block (putting the leeway under the wrong key as a map); template renderers emitting structured duration objects; editing config via UIs that serialize durations as objects.","solutions":["Send NotBeforeLeeway as a duration string like \"30s\" or \"2m\".","Or send a nanosecond number (e.g. 30000000000 for 30s).","Validate the rendered JSON payload shape before POSTing it to the API."],"exampleFix":"// before\n{\"NotBeforeLeeway\": {\"value\": 30, \"unit\": \"s\"}}\n// after\n{\"NotBeforeLeeway\": \"30s\"}","handlingStrategy":"type-guard","validationCode":"switch v := raw[\"NotBeforeLeeway\"].(type) {\ncase string, float64:\n\t// ok\ndefault:\n\treturn fmt.Errorf(\"NotBeforeLeeway must be a duration string or number, got %T\", v)\n}","typeGuard":"func isDurationScalar(v interface{}) bool {\n\tswitch v.(type) {\n\tcase string, float64, float32, int64:\n\t\treturn true\n\t}\n\treturn false\n}","tryCatchPattern":"if err := json.Unmarshal(payload, &am); err != nil {\n\tif strings.Contains(err.Error(), \"unexpected NotBeforeLeeway type\") {\n\t\treturn fmt.Errorf(\"send NotBeforeLeeway as e.g. \\\"30s\\\" or a nanosecond number\")\n\t}\n\treturn err\n}","preventionTips":["Same rule as ExpirationLeeway: string duration or nanosecond number only","Diff rendered JSON against a known-good auth-method payload before POST","Validate payload shape in CI for generated configs"],"tags":["json","unmarshal","acl","nomad"],"backgroundTag":"json-unmarshal-type-mismatch","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}