hashicorp/nomad · error
cannot upload management tokens
Error message
cannot upload management tokens
What it means
Create with topologies/capacity requires a plugin with a CSI controller (ControllerRequired), but the referenced plugin is node-only. Controller-less plugins cannot provision volumes, so Nomad rejects the create with this message.
Source
Thrown at api/acl.go:153
var resp ACLToken
wm, err := a.client.put("/v1/acl/token", token, &resp, q)
if err != nil {
return nil, nil, err
}
return &resp, wm, nil
}
// Upload is used to create a client token with pre-specified AccessorID and
// SecretID. Management tokens cannot be uploaded and must be created with Create.
func (a *ACLTokens) Upload(token *ACLToken, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
if token.AccessorID == "" {
return nil, nil, errors.New("missing accessor ID")
}
if token.SecretID == "" {
return nil, nil, errors.New("missing secret ID")
}
if token.Type == "management" {
return nil, nil, errors.New("cannot upload management tokens")
}
var resp ACLToken
wm, err := a.client.put("/v1/acl/token/"+token.AccessorID, token, &resp, q)
if err != nil {
return nil, nil, err
}
return &resp, wm, nil
}
// Update is used to update an existing token
func (a *ACLTokens) Update(token *ACLToken, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
if token.AccessorID == "" {
return nil, nil, errors.New("missing accessor ID")
}
var resp ACLToken
wm, err := a.client.put("/v1/acl/token/"+token.AccessorID,
token, &resp, q)
if err != nil {View on GitHub (pinned to 482b49bf1a)
Solutions
- Add a controller plugin task to the plugin job and re-register (nomad job stop/start) so ControllerRequired is satisfied
- Use `nomad volume register` (static registration) instead of dynamic create for controller-less drivers
- Verify with nomad plugin status <id> that Controllers > 0
Example fix
// before: node-only CSI plugin
plugin "hostpath" {
type = "node"
}
// after: dynamic provisioning needs a controller too
plugin "hostpath" {
type = "controller"
}
plugin "hostpath" {
type = "node"
} Defensive patterns
Strategy: validation
Validate before calling
plug, _, _ := client.CSIPlugins().Get(pluginID, nil)
if plug == nil || plug.ControllersExpected == 0 {
// controller-less driver: use static volume register instead of create
} Try / catch
if err := create(req); err != nil && strings.Contains(err.Error(), "plugin has no controller") {
// fall back to static registration or deploy a controller plugin
} Prevention
- Register both controller and node tasks for dynamic provisioning drivers
- Check nomad plugin status Controllers count before create
- Choose drivers that implement ControllerCreate for dynamic workflows
When it happens
Trigger: `nomad volume create` (Create RPC) whose volume's plugin_id points to a plugin registered only as type="node" (no controller task) — e.g. hostpath or node-only CSI drivers.
Common situations: Registering a CSI plugin job with only node plugins, then trying dynamic provisioning; driver (e.g. some local/path provisioners) simply has no controller; typo'd plugin ID resolving to a node-only plugin.
Related errors
- failed to find instances of controller plugin %q
- error parsing: root should be an object
- missing policy name
- cannot specify Accessor ID
- missing accessor ID
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/128a91ce1c9aa669.
Report an issue: GitHub.