hashicorp/nomad · error

management token cannot be associated with policies or roles

Error message

management token cannot be associated with policies or roles

What it means

Appended by ACLToken.Validate when a management-type token has policies or roles attached. Management tokens implicitly grant all privileges and cannot be scoped; only client tokens carry explicit policy/role links.

Solutions

  1. Set Policies and Roles to empty lists when Type is management
  2. Keep Type as client if you need the attached policies/roles
  3. Strip the policy/role fields when converting a token to management type

Example fix

// before
token.Type = "management" // Policies still populated
// after
token.Type = "management"
token.Policies = []string{}
token.Roles = []string{}
Defensive patterns

Strategy: validation

Validate before calling

if token.Type == "management" && (len(token.Policies) != 0 || len(token.Roles) != 0) {
    return errors.New("management tokens cannot have policies or roles")
}

Type guard

func managementTokenUnscoped(t *structs.ACLToken) bool {
    return t.Type != structs.ACLManagementToken || (len(t.Policies) == 0 && len(t.Roles) == 0)
}

Try / catch

if err := token.Validate(minTTL, maxTTL, nil); err != nil {
    if strings.Contains(err.Error(), "management token cannot be associated") {
        token.Policies, token.Roles = nil, nil
        err = token.Validate(minTTL, maxTTL, nil)
    }
}

Prevention

When it happens

Trigger: Upserting an ACLToken with Type == "management" while Policies or Roles is non-empty.

Common situations: Cloning an existing client token and changing only its Type to management while keeping the old policy/role lists; scripts that copy token structs and flip the type field.

Related errors


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

Appendix: source

Thrown at nomad/structs/acl.go:775

func (a *ACLToken) Validate(minTTL, maxTTL time.Duration, existing *ACLToken) error {
	var mErr multierror.Error

	// The human friendly name of an ACL token cannot exceed 256 characters.
	if len(a.Name) > maxTokenNameLength {
		mErr.Errors = append(mErr.Errors, errors.New("token name too long"))
	}

	// The type of an ACL token must be set. An ACL token of type client must
	// have associated policies or roles, whereas a management token cannot be
	// associated with policies.
	switch a.Type {
	case ACLClientToken:
		if len(a.Policies) == 0 && len(a.Roles) == 0 {
			mErr.Errors = append(mErr.Errors, errors.New("client token missing policies or roles"))
		}
	case ACLManagementToken:
		if len(a.Policies) != 0 || len(a.Roles) != 0 {
			mErr.Errors = append(mErr.Errors, errors.New("management token cannot be associated with policies or roles"))
		}
	default:
		mErr.Errors = append(mErr.Errors, errors.New("token type must be client or management"))
	}

	// There are different validation rules depending on whether the ACL token
	// is being created or updated.
	switch existing {
	case nil:
		if a.ExpirationTTL < 0 {
			mErr.Errors = append(mErr.Errors,
				fmt.Errorf("token expiration TTL '%s' should not be negative", a.ExpirationTTL))
		}

		if a.ExpirationTime != nil && !a.ExpirationTime.IsZero() {

			if a.CreateTime.After(*a.ExpirationTime) {
				mErr.Errors = append(mErr.Errors, errors.New("expiration time cannot be before create time"))

View on GitHub (pinned to 482b49bf1a)