hashicorp/nomad · warning
users: release of unused uid/gid
Error message
users: release of unused uid/gid
What it means
Sentinel ErrReleaseUnused from the dynamic users pool: Pool.Release was called with a uid/gid that is not currently in the pool's used set, meaning the caller tried to return an id that was never allocated or was already released.
Source
Thrown at helper/users/dynamic/pool.go:20
// SPDX-License-Identifier: BUSL-1.1
// Package dynamic provides a way of allocating UID/GID to be used by Nomad
// tasks with no associated service users managed by the operating system.
package dynamic
import (
"errors"
"math/rand"
"strconv"
"sync"
"github.com/hashicorp/go-set/v3"
"github.com/hashicorp/nomad/helper"
)
var (
ErrPoolExhausted = errors.New("users: uid/gid pool exhausted")
ErrReleaseUnused = errors.New("users: release of unused uid/gid")
ErrCannotParse = errors.New("users: unable to parse uid/gid from username")
)
// none indicates no dynamic user
const none = 0
// doNotEnable indicates functionality should be disabled
const doNotEnable = -1
// A UGID is a combination User (UID) and Group (GID). Since Nomad is
// allocating these values together from the same pool it can ensure they are
// always matching values, thus encoding them with one value.
type UGID int
// String returns the string representation of a UGID.
//
// It's just the numbers.
func (id UGID) String() string {View on GitHub (pinned to 482b49bf1a)
Solutions
- Only release ids previously obtained from the pool's allocation
- Guard against double-release; check ownership before calling Release
- Treat as a bookkeeping bug in the caller
Example fix
// before
if err := pool.Release(ugid); err != nil { return err }
// after: track and clear
if ugid != 0 {
if err := pool.Release(ugid); err != nil {
if !errors.Is(err, dynamic.ErrReleaseUnused) { return err }
}
ugid = 0 // prevent double release
} Defensive patterns
Strategy: try-catch
Validate before calling
// only release ids actually acquired and not yet released
if ugid == 0 || ugid < min || ugid > max || alreadyReleased[ugid] {
return nil
} Type guard
func releaseable(ugid UGID, min, max UGID, released map[UGID]bool) bool {
return ugid >= min && ugid <= max && !released[ugid]
} Try / catch
if err := p.Release(ugid); err != nil && !errors.Is(err, dynamic.ErrReleaseUnused) {
return err // ignore benign double-release
} Prevention
- Track each acquired ugID per allocation and clear it after Release
- Never release hardcoded IDs outside the Acquire lifecycle
- Make Release idempotent in caller code
- Log ErrReleaseUnused at debug level rather than failing teardown
When it happens
Trigger: Calling Pool.Release(id) with an id that was never returned by Acquire, or calling Release twice with the same id.
Common situations: Double-release in client teardown paths; releasing a hardcoded/stale ugID after config changed the pool range; a client restarted with a different pool releasing IDs from the old run.
Related errors
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/435bf21ec1fa7f43.
Report an issue: GitHub.