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.

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)

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.