Local configuration overrides glab-managed keys that glab writes only globally

Problem to solve

A repository's local configuration (.git/glab-cli/config.yml) can override keys that glab sets automatically, but glab writes those keys only to the global configuration. A stale local value then wins on every read, while every write goes to a file that's never read.

For the Duo CLI, this makes glab duo cli update download the same version on every run in that repository:

$ glab duo cli update
Update available: 9.5.0 → 9.26.0
Downloading duo-darwin-arm64 version 9.26.0...
✓ Successfully updated to version 9.26.0
$ glab duo cli update
Update available: 9.5.0 → 9.26.0
...

The local file held duo_cli_binary_version: "9.5.0", probably left over from simulating an old install. binarymgr.Runner.SaveBinaryInfo writes 9.26.0 to ~/.config/glab-cli/config.yml, but the next run reads 9.5.0 from the local file again.

glab config set writes to the local file by default inside a repository. That's the easy way to end up here: glab config set duo_cli_binary_version 9.5.0 run in a checkout.

Steps to reproduce

cd "$(mktemp -d)" && git init -q
export GLAB_CONFIG_DIR="$(mktemp -d)"
glab config set duo_cli_binary_version 9.5.0            # writes .git/glab-cli/config.yml
glab config set --global duo_cli_binary_version 9.26.0
glab config get duo_cli_binary_version                  # 9.5.0

Affected keys

The keys described as "automatically set" in internal/config/schema.go:

  • duo_cli_binary_path, duo_cli_binary_version, duo_cli_binary_checksum, duo_cli_last_update_check
  • orbit_cli_binary_path, orbit_cli_binary_version, orbit_cli_binary_checksum, orbit_cli_last_update_check
  • the post-upgrade banner and glab whatsnew version keys

Scope: ScopeGlobal doesn't cover this. It means "not per host", and ordinary ScopeGlobal keys such as git_protocol and branch_prefix are meant to be set per repository.

Proposal

Add a KeyDef attribute (for example GlobalOnly) for glab-managed state, and set it on the keys above. For those keys:

  1. Reads skip the local configuration.
  2. glab config set without --global writes them to the global file, or rejects the local write with an error that names --global.

Either change alone breaks the loop. Doing both also stops new local values from being written.

  • glab config get --global registers --global but never reads it (internal/commands/config/get/config_get.go), so in the reproduction above it also returns 9.5.0. Worth a separate fix.
  • #8568 (closed): local config is not shared across Git worktrees.
  • Found while reviewing !3981 (merged).