hashicorp/nomad · error
client token missing policies or roles
Error message
client token missing policies or roles
What it means
A validation error from ACLToken.Validate: a client-type ACL token must be associated with at least one policy or one role. A token of type client with empty Policies and empty Roles is rejected at create/update time because it would grant nothing useful (or ambiguously nothing).
Source
Thrown at nomad/structs/acl.go:771
}
}
// Validate is used to check a token for reasonableness
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))
}
View on GitHub (pinned to 482b49bf1a)
Solutions
- Attach at least one policy name to the token's Policies list
- Or assign at least one role via the Roles list
- Use Type "management" only if unrestricted access is actually intended
- Validate client tokens carry policies or roles before calling the API
Example fix
// before
token := &api.ACLToken{Name: "ci", Type: "client"}
// after
token := &api.ACLToken{Name: "ci", Type: "client", Policies: []string{"read-only"}} Defensive patterns
Strategy: validation
Validate before calling
if token.Type == "client" && len(token.Policies) == 0 && len(token.Roles) == 0 {
return errors.New("client tokens require at least one policy or role")
} Type guard
func clientTokenHasGrants(t *structs.ACLToken) bool {
return t.Type != structs.ACLClientToken || len(t.Policies) > 0 || len(t.Roles) > 0
} Try / catch
if err := token.Validate(minTTL, maxTTL, nil); err != nil {
if strings.Contains(err.Error(), "client token missing policies or roles") {
token.Policies = append(token.Policies, "default")
err = token.Validate(minTTL, maxTTL, nil)
}
} Prevention
- Attach at least one policy or role when creating client tokens
- Run ACLToken.Validate locally before the RPC call
- Audit token-creation templates to ensure grants are populated
When it happens
Trigger: Upserting an ACLToken with Type == "client" while len(Policies) == 0 and len(Roles) == 0.
Common situations: Automation creating tokens from templates that forget to attach policies; token creation UI/API where role assignment was skipped; refactors that moved permissions from policies to roles but left both lists empty.
Related errors
- token name too long
- management token cannot be associated with policies or roles
- token type must be client or management
- 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/8ad79cfc7bd8171c.
Report an issue: GitHub.