hashicorp/nomad · error

token type must be client or management

Error message

token type must be client or management

What it means

Sentinel appended by ACLToken.Validate when a.Type matches neither ACLClientToken nor ACLManagementToken — the token's Type field was left empty or set to an invalid value.

Source

Thrown at nomad/structs/acl.go:778

	// 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"))
			}

			// Create a time duration which details the time-til-expiry, so we can

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Set Type explicitly to "client" or "management" on the token
  2. Validate the Type field before submitting the token request
  3. Check for client-side code that omits zero-value enum fields

Example fix

// before
token := &api.ACLToken{Name: "ci", Policies: []string{"read"}}
// after
token := &api.ACLToken{Name: "ci", Type: "client", Policies: []string{"read"}}
Defensive patterns

Strategy: validation

Validate before calling

if token.Type != "client" && token.Type != "management" {
    return fmt.Errorf("token type must be client or management, got %q", token.Type)
}

Type guard

func validTokenType(t string) bool {
    return t == structs.ACLClientToken || t == structs.ACLManagementToken
}

Try / catch

if err := token.Validate(minTTL, maxTTL, nil); err != nil {
    if strings.Contains(err.Error(), "token type must be client or management") {
        token.Type = structs.ACLClientToken
        err = token.Validate(minTTL, maxTTL, nil)
    }
}

Prevention

When it happens

Trigger: Upserting an ACLToken whose Type field is "", mistyped, or any value other than ACLClientToken/ACLManagementToken.

Common situations: Hand-constructed token structs missing the Type field; API clients sending lowercase/alternate casing; version changes where tokens were created without an explicit type.

Related errors


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