{"record":{"id":"435bf21ec1fa7f43","repo":"hashicorp/nomad","slug":"users-release-of-unused-uid-gid","errorCode":null,"errorMessage":"users: release of unused uid/gid","messagePattern":"users: release of unused uid/gid","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"helper/users/dynamic/pool.go","lineNumber":20,"sourceCode":"// SPDX-License-Identifier: BUSL-1.1\n\n// Package dynamic provides a way of allocating UID/GID to be used by Nomad\n// tasks with no associated service users managed by the operating system.\npackage dynamic\n\nimport (\n\t\"errors\"\n\t\"math/rand\"\n\t\"strconv\"\n\t\"sync\"\n\n\t\"github.com/hashicorp/go-set/v3\"\n\t\"github.com/hashicorp/nomad/helper\"\n)\n\nvar (\n\tErrPoolExhausted = errors.New(\"users: uid/gid pool exhausted\")\n\tErrReleaseUnused = errors.New(\"users: release of unused uid/gid\")\n\tErrCannotParse   = errors.New(\"users: unable to parse uid/gid from username\")\n)\n\n// none indicates no dynamic user\nconst none = 0\n\n// doNotEnable indicates functionality should be disabled\nconst doNotEnable = -1\n\n// A UGID is a combination User (UID) and Group (GID). Since Nomad is\n// allocating these values together from the same pool it can ensure they are\n// always matching values, thus encoding them with one value.\ntype UGID int\n\n// String returns the string representation of a UGID.\n//\n// It's just the numbers.\nfunc (id UGID) String() string {","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/helper/users/dynamic/pool.go#L2-L38","documentation":"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.","triggerScenarios":"Calling Pool.Release(id) with an id that was never returned by Acquire, or calling Release twice with the same id.","commonSituations":"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.","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"],"exampleFix":"// before\nif err := pool.Release(ugid); err != nil { return err }\n// after: track and clear\nif ugid != 0 {\n  if err := pool.Release(ugid); err != nil {\n    if !errors.Is(err, dynamic.ErrReleaseUnused) { return err }\n  }\n  ugid = 0 // prevent double release\n}","handlingStrategy":"try-catch","validationCode":"// only release ids actually acquired and not yet released\nif ugid == 0 || ugid < min || ugid > max || alreadyReleased[ugid] {\n  return nil\n}","typeGuard":"func releaseable(ugid UGID, min, max UGID, released map[UGID]bool) bool {\n  return ugid >= min && ugid <= max && !released[ugid]\n}","tryCatchPattern":"if err := p.Release(ugid); err != nil && !errors.Is(err, dynamic.ErrReleaseUnused) {\n  return err // ignore benign double-release\n}","preventionTips":["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"],"tags":["go","nomad","pool","lifecycle"],"backgroundTag":"double-release-resource","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}