{"record":{"id":"99917eae1541fe33","repo":"hashicorp/nomad","slug":"cannot-update-expiration-ttl","errorCode":null,"errorMessage":"cannot update expiration TTL","messagePattern":"cannot update expiration TTL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/acl.go","lineNumber":815,"sourceCode":"\t\t\t// check this against the regions max and min values.\n\t\t\texpiresIn := a.ExpirationTime.Sub(a.CreateTime)\n\t\t\tif expiresIn > maxTTL {\n\t\t\t\tmErr.Errors = append(mErr.Errors,\n\t\t\t\t\tfmt.Errorf(\"expiration time cannot be more than %s in the future (was %s)\",\n\t\t\t\t\t\tmaxTTL, expiresIn))\n\n\t\t\t} else if expiresIn < minTTL {\n\t\t\t\tmErr.Errors = append(mErr.Errors,\n\t\t\t\t\tfmt.Errorf(\"expiration time cannot be less than %s in the future (was %s)\",\n\t\t\t\t\t\tminTTL, expiresIn))\n\t\t\t}\n\t\t}\n\tdefault:\n\t\tif existing.Global != a.Global {\n\t\t\tmErr.Errors = append(mErr.Errors, errors.New(\"cannot toggle global mode\"))\n\t\t}\n\t\tif existing.ExpirationTTL != a.ExpirationTTL {\n\t\t\tmErr.Errors = append(mErr.Errors, errors.New(\"cannot update expiration TTL\"))\n\t\t}\n\t\tif a.ExpirationTime != nil {\n\t\t\tif !existing.ExpirationTime.Equal(*a.ExpirationTime) {\n\t\t\t\tmErr.Errors = append(mErr.Errors, errors.New(\"cannot update expiration time\"))\n\t\t\t}\n\t\t}\n\n\t}\n\n\treturn mErr.ErrorOrNil()\n}\n\n// HasExpirationTime checks whether the ACL token has an expiration time value\n// set.\nfunc (a *ACLToken) HasExpirationTime() bool {\n\tif a == nil || a.ExpirationTime == nil {\n\t\treturn false\n\t}","sourceCodeStart":797,"sourceCodeEnd":833,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/acl.go#L797-L833","documentation":"A validation error from ACLToken.Validate on update: a token's ExpirationTTL is immutable after creation. Nomad rejects updates where the submitted TTL differs from the existing token's ExpirationTTL.","triggerScenarios":"Calling ACL Upsert (update) on an existing token with a modified ExpirationTTL value (including zeroing it or setting it on a token created without one).","commonSituations":"Automation extending/renewing token lifetime by editing the TTL; token renewal scripts that mistakenly update instead of recreating; config drift tools rewriting all fields to defaults.","solutions":["Leave ExpirationTTL unchanged when updating the token","To change lifetime, create a new token with the desired TTL and migrate workloads","Copy ExpirationTTL verbatim from the existing token into the update request"],"exampleFix":"// before\nupdate := existing\nupdate.ExpirationTTL = 72 * time.Hour // immutable\n// after\nupdate := existing // ExpirationTTL unchanged\nnewToken := &api.ACLToken{Name: existing.Name, Type: existing.Type, Policies: existing.Policies, ExpirationTTL: 72 * time.Hour}\napiClient.ACL().Create(newToken, nil)","handlingStrategy":"validation","validationCode":"if existing != nil && existing.ExpirationTTL != update.ExpirationTTL {\n    return errors.New(\"ExpirationTTL is immutable; create a new token to change lifetime\")\n}","typeGuard":"func expirationTTLStable(existing, update *structs.ACLToken) bool {\n    return existing == nil || existing.ExpirationTTL == update.ExpirationTTL\n}","tryCatchPattern":"if err := update.Validate(minTTL, maxTTL, existing); err != nil {\n    if strings.Contains(err.Error(), \"cannot update expiration TTL\") {\n        update.ExpirationTTL = existing.ExpirationTTL\n        err = update.Validate(minTTL, maxTTL, existing)\n    }\n}","preventionTips":["Never modify ExpirationTTL on updates; copy it from the existing token","Implement token rotation (create new, migrate, delete old) instead of editing TTLs","Validate updates against the existing token before submission"],"tags":["nomad","acl","token","immutability","validation"],"backgroundTag":"immutable-field-modified","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"}