hashicorp/nomad · error
ACL role with name %s already exists
Error message
ACL role with name %s already exists
What it means
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.
Source
Thrown at nomad/state/state_store_acl.go:133
// If we did not find an ACL Role within state with the same name, we need
// to check using the ID index as the operator might be performing an
// update on the role name.
//
// If we found an entry using the name index, we need to check that the ID
// matches the object within the request.
if existingRaw == nil {
existingRaw, err = txn.First(TableACLRoles, indexID, role.ID)
if err != nil {
return false, fmt.Errorf("ACL role lookup failed: %v", err)
}
if existingRaw != nil {
existing = existingRaw.(*structs.ACLRole)
}
} else {
existing = existingRaw.(*structs.ACLRole)
if existing.ID != role.ID {
return false, fmt.Errorf("ACL role with name %s already exists", role.Name)
}
}
// Depending on whether this is an initial create, or an update, we need to
// check and set certain parameters. The most important is to ensure any
// create index is carried over.
if existing != nil {
// If the role already exists, check whether the update contains any
// difference. If it doesn't, we can avoid a state update as wel as
// updates to any blocking queries.
if existing.Equal(role) {
return false, nil
}
role.CreateIndex = existing.CreateIndex
role.ModifyIndex = index
} else {View on GitHub (pinned to 482b49bf1a)
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.
Example fix
// before
createResp, _, _ := client.ACL().Roles().Create(&api.ACLRole{Name: "deploy-admin", Policies: [...]}, nil)
// after (reuse existing role ID for update)
roles, _, _ := client.ACL().Roles().List(nil)
var roleID string
for _, r := range roles { if r.Name == "deploy-admin" { roleID = r.ID } }
_, _, _ = client.ACL().Roles().Update(&api.ACLRole{ID: roleID, Name: "deploy-admin", Policies: [...]}, nil) Defensive patterns
Strategy: validation
Validate before calling
roles, _, _ := client.ACL().Roles().List(nil)
for _, r := range roles {
if r.Name == newRole.Name && r.ID != newRole.ID {
return fmt.Errorf("role name %q already exists with ID %s", r.Name, r.ID)
}
} Try / catch
_, _, err := client.ACL().Roles().Create(role, nil)
if err != nil && strings.Contains(err.Error(), "already exists") {
// fetch existing role by name and reuse its ID to update instead
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ACL policy not found
- ACL role not found
- detected corrupted token within the state store: missing rol
- ACL binding rule not found
- ACL auth method not found
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/7206842bf9708e84.
Report an issue: GitHub.