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 canView on GitHub (pinned to 482b49bf1a)
Solutions
- Set Type explicitly to "client" or "management" on the token
- Validate the Type field before submitting the token request
- 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
- Always set Type explicitly; the Go zero value "" is invalid
- Use the structs.ACLClientToken/ACLManagementToken constants instead of raw strings
- Validate tokens client-side before submission
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
- token name too long
- client token missing policies or roles
- management token cannot be associated with policies or roles
- expiration time cannot be before create time
- cannot toggle global mode
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/84686cdbca614115.
Report an issue: GitHub.