Caleb Madara opened merge request !3117: fix(cmdutils): Avoid race on global viper state in GroupOverride at GitLab.org / cli

Caleb Madara
2026-04-16T07:03:35Z

Description

GroupOverride in internal/cmdutils/flag.go called viper.SetEnvPrefix and viper.BindEnv on every invocation. These functions write to a single global *Viper object shared across the entire process. Go maps are not goroutine-safe, so when parallel tests called GroupOverride concurrently, they raced on v.env , the internal map viper uses to store env bindings.

The fix introduces a package-level private *viper.Viper instance dedicated solely to resolving GITLAB_GROUP. It is initialised once at package load time using MustBindEnv for explicit error signalling. Because no other goroutine holds a reference to it, concurrent calls to GroupOverride are race-safe.

Updates

Following an architectural suggestion, the implementation was updated to use a private *viper.Viper instance instead of the global one.

The original approach of moving SetEnvPrefix and BindEnv into an init function fixed the race but introduced a subtle regression: any call to viper.Reset() would wipe the global instance, causing GroupOverride to silently return an empty string for GITLAB_GROUP. The original code was resilient to this because it re-applied the setup on every call.

The new approach uses a dedicated package-level *viper.Viper instance that is unreachable by viper.Reset() or any other global viper call, giving both race safety and resilience to global state resets.

Related Issues

Resolves #8258

How has this been tested?

The race was reproduced locally using the -race flag and -count=5 to increase the chance of goroutine overlap:

go test -race -count=5 ./internal/commands/token/rotate/...

Before the fix this reliably produced:

WARNING: DATA RACE
Write at ... github.com/spf13/viper.(*Viper).BindEnv()
gitlab.com/gitlab-org/cli/internal/cmdutils.GroupOverride()
internal/cmdutils/flag.go:11
Previous write at ... github.com/spf13/viper.(*Viper).BindEnv()
gitlab.com/gitlab-org/cli/internal/cmdutils.GroupOverride()
internal/cmdutils/flag.go:11
FAIL gitlab.com/gitlab-org/cli/internal/commands/token/rotate

After the fix all runs pass cleanly:

go test -race -count=5 ./internal/commands/token/rotate/... ./internal/cmdutils/...
ok gitlab.com/gitlab-org/cli/internal/commands/token/rotate 1.260s
ok gitlab.com/gitlab-org/cli/internal/cmdutils 13.697s