hashicorp/nomad · error

cannot toggle global mode

Error message

cannot toggle global mode

What it means

A validation error from ACLToken.Validate on update: when updating an existing token, the Global flag is immutable. Nomad rejects an update whose Global value differs from the existing token's Global value.

Source

Thrown at nomad/structs/acl.go:812

			}

			// Create a time duration which details the time-til-expiry, so we can
			// check this against the regions max and min values.
			expiresIn := a.ExpirationTime.Sub(a.CreateTime)
			if expiresIn > maxTTL {
				mErr.Errors = append(mErr.Errors,
					fmt.Errorf("expiration time cannot be more than %s in the future (was %s)",
						maxTTL, expiresIn))

			} else if expiresIn < minTTL {
				mErr.Errors = append(mErr.Errors,
					fmt.Errorf("expiration time cannot be less than %s in the future (was %s)",
						minTTL, expiresIn))
			}
		}
	default:
		if existing.Global != a.Global {
			mErr.Errors = append(mErr.Errors, errors.New("cannot toggle global mode"))
		}
		if existing.ExpirationTTL != a.ExpirationTTL {
			mErr.Errors = append(mErr.Errors, errors.New("cannot update expiration TTL"))
		}
		if a.ExpirationTime != nil {
			if !existing.ExpirationTime.Equal(*a.ExpirationTime) {
				mErr.Errors = append(mErr.Errors, errors.New("cannot update expiration time"))
			}
		}

	}

	return mErr.ErrorOrNil()
}

// HasExpirationTime checks whether the ACL token has an expiration time value
// set.
func (a *ACLToken) HasExpirationTime() bool {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Keep the existing token's Global value unchanged on update
  2. If the region scope must change, delete and recreate the token with the desired Global setting
  3. Set Global to the same value read from the existing token before submitting the update

Example fix

// before
update := existing
update.Global = !existing.Global // toggling scope
// after
update := existing
// keep update.Global as-is; recreate token if scope must change
apiClient.ACL().Delete(existing.AccessorID, nil)
newToken.Global = desiredScope
apiClient.ACL().Create(newToken, nil)
Defensive patterns

Strategy: validation

Validate before calling

if existing != nil && existing.Global != update.Global {
    return errors.New("Global is immutable; recreate the token to change region scope")
}

Type guard

func globalFlagStable(existing, update *structs.ACLToken) bool {
    return existing == nil || existing.Global == update.Global
}

Try / catch

if err := update.Validate(minTTL, maxTTL, existing); err != nil {
    if strings.Contains(err.Error(), "cannot toggle global mode") {
        update.Global = existing.Global
        err = update.Validate(minTTL, maxTTL, existing)
    }
}

Prevention

When it happens

Trigger: Calling ACL Upsert (update) on an existing token where the new token flips Global from true to false or vice versa.

Common situations: Scripts copying a global token into a local-region edit (or the reverse); UIs exposing the global checkbox on token edit forms; automation that normalizes tokens and rewrites the global field.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/680c503356e41c32. Report an issue: GitHub.