fix: route deprecation warnings to stderr

Summary

Cobra's c.Print() uses OutOrStderr(), which respects whatever was set via SetOut. Because NewCmdRoot was calling rootCmd.SetOut(f.IO().StdOut), every diagnostic message cobra emitted — pflag flag-deprecation warnings, "unknown help topic", usage-on-error — landed on stdout and corrupted piped command output. The user-visible failure mode reported in #8371 (closed): glab variable export --format json | jq breaks because the deprecation warning is mixed into the JSON stream.

This is the long-standing spf13/cobra#1708.

Approach

Match GitHub CLI's fix in cli/cli's pkg/cmd/root/root.go — drop the rootCmd.SetOut/SetErr wiring entirely:

  • Cobra's c.Print falls back to os.Stderr when Out is unset, which is the right destination for every cobra-internal Print site (deprecation warnings, unknown help topic, version-flag declaration errors, etc.).
  • Plumb f.IO() explicitly into RootHelpFunc (which writes help to streams.StdOut) and RootUsageFunc (which writes usage to streams.StdErr, since usage is a diagnostic).

Also fix the same bug class in internal/utils/utils.goIsEnvVarEnabled and PrintDeprecationWarning were writing WARNING: and DEPRECATION WARNING: messages directly to os.Stdout, with the same JSON-corruption failure mode (e.g. setting NO_PROMPT=true instead of GLAB_NO_PROMPT).

Test coverage

Three regression nets in internal/commands/root_test.go:

  1. TestRootDoesNotWireCobraOutToStdOut — structural assertion that rootCmd.Out is not wired to StdOut. Catches direct reintroduction of rootCmd.SetOut(f.IO().StdOut).
  2. TestDeprecationWarningStaysOffStdOut — synthetic command with a deprecated flag exercised end-to-end through NewCmdRoot.Execute.
  3. TestAllDeprecatedFlagsRouteToStderr — walks the real command tree, finds every flag with Deprecated != "" (17 today across mr, issue, incident, ci, release, variable), and exercises each through ParseFlags. Auto-scales as new deprecated flags are added.

Verified by temporarily reintroducing rootCmd.SetOut(f.IO().StdOut) — all three tests + all 17 subtests fail. Reverted, everything green.

Test plan

  • go test ./internal/... passes (except the pre-existing TestGitCommonDir/worktree macOS symlink failure, which this MR also fixes as a separate commit)
  • go vet ./... clean
  • Manual verification of the original failure mode: glab variable export --format json 1>out 2>err — stdout clean, deprecation on stderr
  • Manual verification of jq pipe: glab mr list --mine -F json | jq 'length' — jq parses cleanly, deprecation on stderr
  • Manual verification of --help (4528 bytes on stdout, 0 on stderr) and --version (12 bytes on stdout, 0 on stderr)
  • Manual verification of unknown subcommand: glab totallybogus — 0 bytes stdout, error on stderr
  • Manual verification of NO_PROMPT=true glab --versionDEPRECATION WARNING now on stderr (was previously on stdout)

Closes #8371 (closed)

See also: spf13/cobra#1708

Merge request reports

Loading