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

  1. Fetch the existing role via ACLRole.List/Get by name and reuse its ID in the upsert (update instead of create).
  2. Choose a different, unique Name for the new role.
  3. Delete the existing role (DeleteACLRolesByID) if it is stale, then create the new one.
  4. 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

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


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/7206842bf9708e84. Report an issue: GitHub.