hashicorp/nomad · error
modifying node pool %q is not allowed
Error message
modifying node pool %q is not allowed
What it means
upsertNodePoolTxn blocks any update to a built-in node pool (e.g. default, all). When an existing pool is found and the submitted pool IsBuiltIn(), the upsert returns this error instead of applying changes, protecting Nomad's internal pools from operator modification.
Source
Thrown at nomad/state/state_store_node_pools.go:136
}
return txn.Commit()
}
func (s *StateStore) upsertNodePoolTxn(txn *txn, index uint64, pool *structs.NodePool) error {
if pool == nil {
return nil
}
existing, err := txn.First(TableNodePools, "id", pool.Name)
if err != nil {
return fmt.Errorf("node pool lookup failed: %w", err)
}
if existing != nil {
// Prevent changes to built-in node pools.
if pool.IsBuiltIn() {
return fmt.Errorf("modifying node pool %q is not allowed", pool.Name)
}
exist := existing.(*structs.NodePool)
pool.CreateIndex = exist.CreateIndex
pool.ModifyIndex = index
} else {
pool.CreateIndex = index
pool.ModifyIndex = index
}
if err := txn.Insert(TableNodePools, pool); err != nil {
return fmt.Errorf("node pool insert failed: %w", err)
}
return nil
}
// fetchOrCreateNodePoolTxn returns an existing node pool with the given nameView on GitHub (pinned to 482b49bf1a)
Solutions
- Only include custom (non-built-in) node pools in the upsert payload; skip pools where pool.IsBuiltIn() is true.
- If tuning behavior of built-in pools is the goal, create a custom node pool and assign nodes/workloads to it instead.
- Filter the response of node pool list APIs before replaying them as updates in automation.
- On newer Nomad versions, check whether the desired setting is supported for built-in pools; otherwise keep them untouched.
Example fix
// before
for _, pool := range pools {
stateStore.UpsertNodePools(idx, []*structs.NodePool{pool}) // includes "default"
}
// after
for _, pool := range pools {
if !pool.IsBuiltIn() {
stateStore.UpsertNodePools(idx, []*structs.NodePool{pool})
}
} Defensive patterns
Strategy: validation
Validate before calling
for _, pool := range pools {
if pool.IsBuiltIn() {
continue // never upsert built-in pools
}
err := s.UpsertNodePools(idx, []*structs.NodePool{pool})
if err != nil {
return err
}
} Type guard
func isUpdatablePool(p *structs.NodePool) bool {
return p != nil && !p.IsBuiltIn()
} Try / catch
err := s.UpsertNodePools(idx, pools)
if err != nil && strings.Contains(err.Error(), "is not allowed") {
// strip built-in pools from the batch and retry with custom pools only
} Prevention
- Filter out built-in pools (default, all) from any node pool update automation.
- Never replay node pool list API responses directly back as updates.
- Create dedicated custom pools when custom scheduler or identity configuration is needed.
- Add CI checks that reject configs referencing built-in pool names in write operations.
When it happens
Trigger: Calling UpsertNodePools (or fetchOrCreateNodePoolTxn resolving a request to update) with a NodePool whose Name matches a built-in pool — e.g. trying to change the description, scheduler config, or node identity ttl of the "default" or "all" pool via the node pool update API/CLI.
Common situations: `nomad node pool update default ...` attempts; Terraform/automation applying node pool configs that include built-in pools; jobs or API clients sending a full pool object back that was fetched from /node/pools and happens to include built-ins.
Related errors
- failed to query node pool: %v
- node pool %s not found
- failed to get job node pool %q: %v
- failed to get job node pool %q: %v
- error parsing: root should be an object
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/974c45aca5e34a1d.
Report an issue: GitHub.