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

  1. Only release ids previously obtained from the pool's allocation
  2. Guard against double-release; check ownership before calling Release
  3. 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

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.