{"record":{"id":"7206842bf9708e84","repo":"hashicorp/nomad","slug":"acl-role-with-name-s-already-exists","errorCode":null,"errorMessage":"ACL role with name %s already exists","messagePattern":"ACL role with name (.+?) already exists","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/state/state_store_acl.go","lineNumber":133,"sourceCode":"\n\t// If we did not find an ACL Role within state with the same name, we need\n\t// to check using the ID index as the operator might be performing an\n\t// update on the role name.\n\t//\n\t// If we found an entry using the name index, we need to check that the ID\n\t// matches the object within the request.\n\tif existingRaw == nil {\n\t\texistingRaw, err = txn.First(TableACLRoles, indexID, role.ID)\n\t\tif err != nil {\n\t\t\treturn false, fmt.Errorf(\"ACL role lookup failed: %v\", err)\n\t\t}\n\t\tif existingRaw != nil {\n\t\t\texisting = existingRaw.(*structs.ACLRole)\n\t\t}\n\t} else {\n\t\texisting = existingRaw.(*structs.ACLRole)\n\t\tif existing.ID != role.ID {\n\t\t\treturn false, fmt.Errorf(\"ACL role with name %s already exists\", role.Name)\n\t\t}\n\t}\n\n\t// Depending on whether this is an initial create, or an update, we need to\n\t// check and set certain parameters. The most important is to ensure any\n\t// create index is carried over.\n\tif existing != nil {\n\n\t\t// If the role already exists, check whether the update contains any\n\t\t// difference. If it doesn't, we can avoid a state update as wel as\n\t\t// updates to any blocking queries.\n\t\tif existing.Equal(role) {\n\t\t\treturn false, nil\n\t\t}\n\n\t\trole.CreateIndex = existing.CreateIndex\n\t\trole.ModifyIndex = index\n\t} else {","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/state/state_store_acl.go#L115-L151","documentation":"Nomad enforces unique ACL role names. During upsert, if the name index found an existing role whose name matches the request but whose ID differs from role.ID, the store rejects it because two distinct roles cannot share a name. This is a deliberate uniqueness-constraint violation, not a system fault.","triggerScenarios":"Calling UpsertACLRoles (ACLRole.Upsert RPC or nomad job ACL bootstrap flows) with a role whose Name already belongs to a different role ID — e.g. creating a new role (different ID) with an existing Name, or updating a role's name to collide with another role.","commonSituations":"Terraform/IaC re-applying role config with a regenerated ID while keeping the same name; two teams independently creating a role named 'deploy-admin'; migration scripts that copy roles between clusters keeping names but not IDs.","solutions":["Fetch the existing role via ACLRole.List/Get by name and reuse its ID in the upsert (update instead of create).","Choose a different, unique Name for the new role.","Delete the existing role (DeleteACLRolesByID) if it is stale, then create the new one.","Ensure IaC state stores and reuses the role ID rather than generating a new one per apply."],"exampleFix":"// before\ncreateResp, _, _ := client.ACL().Roles().Create(&api.ACLRole{Name: \"deploy-admin\", Policies: [...]}, nil)\n// after (reuse existing role ID for update)\nroles, _, _ := client.ACL().Roles().List(nil)\nvar roleID string\nfor _, r := range roles { if r.Name == \"deploy-admin\" { roleID = r.ID } }\n_, _, _ = client.ACL().Roles().Update(&api.ACLRole{ID: roleID, Name: \"deploy-admin\", Policies: [...]}, nil)","handlingStrategy":"validation","validationCode":"roles, _, _ := client.ACL().Roles().List(nil)\nfor _, r := range roles {\n    if r.Name == newRole.Name && r.ID != newRole.ID {\n        return fmt.Errorf(\"role name %q already exists with ID %s\", r.Name, r.ID)\n    }\n}","typeGuard":null,"tryCatchPattern":"_, _, err := client.ACL().Roles().Create(role, nil)\nif err != nil && strings.Contains(err.Error(), \"already exists\") {\n    // fetch existing role by name and reuse its ID to update instead\n}","preventionTips":["Before creating, list roles and check for name collisions.","Reuse existing role IDs in IaC state instead of regenerating.","Adopt naming conventions/ownership per team to avoid collisions.","Prefer upsert-by-ID (fetch then update) over blind create."],"tags":["nomad","acl","uniqueness","name-conflict"],"backgroundTag":"duplicate-resource-name","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"}