{"record":{"id":"9179acae5eb7b800","repo":"hashicorp/nomad","slug":"detected-corrupted-token-within-the-state-store-m","errorCode":null,"errorMessage":"detected corrupted token within the state store: missing role link ID","messagePattern":"detected corrupted token within the state store: missing role link ID","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"nomad/state/state_store_acl.go","lineNumber":323,"sourceCode":"\t// operating on the token directly from state.\n\tcopied := false\n\n\ttoken := original\n\n\t// copyTokenFn is a helper function which copies the ACL token along with\n\t// a certain number of ACL role links.\n\tcopyTokenFn := func(t *structs.ACLToken, numLinks int) *structs.ACLToken {\n\t\tclone := t.Copy()\n\t\tclone.Roles = slices.Clone(t.Roles[:numLinks])\n\t\treturn clone\n\t}\n\n\tfor linkIndex, link := range original.Roles {\n\n\t\t// This should never happen, but guard against it anyway, so we log an\n\t\t// error rather than panic.\n\t\tif link.ID == \"\" {\n\t\t\treturn nil, errors.New(\"detected corrupted token within the state store: missing role link ID\")\n\t\t}\n\n\t\trole, err := s.getACLRoleByIDTxn(txn, nil, link.ID)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif role == nil {\n\t\t\tif !copied {\n\t\t\t\t// clone the token as we cannot touch the original\n\t\t\t\ttoken = copyTokenFn(original, linkIndex)\n\t\t\t\tcopied = true\n\t\t\t}\n\t\t\t// if already owned then we just don't append it.\n\t\t} else if role.Name != link.Name {\n\t\t\tif !copied {\n\t\t\t\ttoken = copyTokenFn(original, linkIndex)\n\t\t\t\tcopied = true","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/state/state_store_acl.go#L305-L341","documentation":"fixTokenRoleLinks repairs ACL tokens whose role links may reference deleted or invalid roles. This error fires when a token's role link has an empty ID — data that should never exist — so the store returns a corruption error instead of panicking or dereferencing an empty key. It is returned by token lookups (ACLTokenByAccessorID/SecretID) while sanitizing the token.","triggerScenarios":"Reading any ACL token via accessor or secret ID when the stored token record contains a Roles[] entry with an empty ID string; typically the result of corrupted state store data, a bad raft snapshot restore, or a bug in older token-write code paths.","commonSituations":"Upgrading from a version where token role links were mishandled; restoring a state snapshot from an unhealthy cluster; manual raft/state manipulation.","solutions":["Identify the corrupt token (accessor ID from the log path) and delete/recreate it: nomad acl token delete -_accessor <id>.","Restore state from a known-good snapshot taken before the corruption.","Upgrade Nomad to the latest patch release; this guard was added for a known corruption class.","If many tokens are affected, contact HashiCorp support and do not hand-edit raft data."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"tok, _, err := client.ACLTokens().GetAccessor(accessorID, nil)\nif err != nil && strings.Contains(err.Error(), \"corrupted token\") {\n    // do not retry blindly: escalate to operator, then delete the token\n    return escalateAndDeleteToken(accessorID)\n}","preventionTips":["Keep regular `nomad operator snapshot save` backups of the state store.","Never hand-edit raft/state data.","Upgrade Nomad promptly when state-store corruption patches are released.","Monitor for this error in server logs as a critical alert."],"tags":["nomad","acl","corruption","state-store","data-integrity"],"backgroundTag":"state-store-corruption","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"}