Skip to content

Resolve ActiveToken for a GH_USER-selected account - #13984

Closed
1fanwang wants to merge 3 commits into
cli:trunkfrom
1fanwang:gh-user-token-selection
Closed

1fanwang wants to merge 3 commits into
cli:trunkfrom
1fanwang:gh-user-token-selection

Conversation

@1fanwang

@1fanwang 1fanwang commented Jul 28, 2026 •

Copy link
Copy Markdown

Why

Multiple authenticated accounts on one host is a long-standing pain (#12145, #12885, #326, #9111, #11938). Switching means gh auth switch, which mutates the shared active-account state, so it races across concurrent shells, scripts, and agent runs. There's no per-invocation way to pick an account.

#12145 asks for a GH_USER env var to select an account. This is the token-resolution half of it.

What

ActiveToken honors GH_USER: when it's set, resolve the selected account's token instead of the stored active account, per host, without changing what any other shell sees. GH_USER=work gh pr create acts as work.

  • An environment GH_TOKEN/GITHUB_TOKEN still wins; GH_USER overrides only the stored active account's config or keyring token.
  • Resolved per host, so a GH_USER that exists on one host doesn't leak another host's active token.
  • When GH_USER is the active user with no keyring entry, it falls through to normal resolution (config token, or a legacy unkeyed keyring token).
  • A different account with no stored token yields nothing, never another account's token.
  • Scoped to token resolution, so it never touches the stored active user that switch/login/logout read and write.

Complementary to the --user flag discussed in #12145: env var for interactive sessions, flag for scripted/agent callers.

Testing

Unit, internal/config (14 tests): non-active selection, stored-active untouched, GH_TOKEN precedence, unauthenticated yields no token, switch unaffected, empty-string ignored, three-account selection, HasActiveToken, legacy-keyring fallback and its no-leak case, config-token override for mixed secure/insecure storage, multi-host resolution with cross-host no-leak, and same-username-per-host. Full package passes.

Manual, two real accounts on github.com: GH_USER=<other> gh api user returns the other account; no GH_USER (and empty GH_USER) returns the stored active; GH_TOKEN overrides GH_USER; an unknown GH_USER returns auth-required.

Process

I know #12145 isn't help wanted yet. This is a reference implementation to make the design concrete, not to bypass the process — happy to hold or reshape until it's labelled. Re: #12145, #12885.

Honor a GH_USER environment variable in ActiveToken: when set (and GH_TOKEN is
not), resolve the token for that already-authenticated account from the keyring,
instead of the stored active account. This lets concurrent shells and scripted/
agent invocations act as different accounts from one shared config, without
gh auth switch mutating global state.

Scoped to token resolution so it never leaks into the stored active user that
switch/login/logout read and write. GH_TOKEN still takes precedence; an
unauthenticated GH_USER yields no token rather than falling back to another
account.

Re: cli#12145
Signed-off-by: 1fanwang <[email protected]>
@1fanwang
1fanwang requested a review from a team as a code owner July 28, 2026 08:04
@github-actions github-actions Bot added external pull request originating outside of the CLI core team needs-triage needs to be reviewed unmet-requirements and removed needs-triage needs to be reviewed labels Jul 28, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your pull request! Unfortunately, it doesn't meet the requirements for review:

  • None of the referenced issues have the help wanted label

Please update your PR to address the above. This PR will be automatically closed in 4 days if these requirements are not met.

Full contribution requirements
  1. Include a detailed description of what this PR does
  2. Link to an issue with the help wanted label (use Fixes #123 or Closes #123)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request updates GitHub CLI’s authentication token resolution so a caller can select an already-authenticated keyring account per invocation via GH_USER, without mutating the stored “active account” state in config.

Changes:

  • Teach AuthConfig.ActiveToken to honor GH_USER (when no token is provided via env/config), resolving the selected user’s keyring token instead of the stored active user.
  • Add unit tests covering GH_USER selection behavior, precedence with GH_TOKEN, and non-mutation of stored active user.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
internal/config/config.go Adds GH_USER handling to ActiveToken to resolve a selected authenticated user’s keyring token without changing stored active account state.
internal/config/auth_config_test.go Adds unit tests validating GH_USER token selection, precedence rules, and that stored active user is unaffected.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/config/config.go Outdated
Comment on lines +248 to +253
if envUser := os.Getenv("GH_USER"); envUser != "" {
if t, err := c.TokenFromKeyringForUser(hostname, envUser); err == nil {
return t, "keyring"
}
return "", ""
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in fe91757. The legacy unkeyed-token fallback is now preserved, but only when GH_USER equals the stored active user, so a different requested account never receives the active user's token. Added regression tests for both the fallback and the no-leak case.

1fanwang added 2 commits July 28, 2026 01:12
When GH_USER's per-user keyring lookup misses, fall back to the legacy unkeyed
keyring slot only when GH_USER equals the stored active user, so legacy keyrings
keep working without ever handing the active user's token to a different account.
Adds regression tests for both the fallback and the no-leak case.

Addresses review feedback on cli#13984.

Signed-off-by: 1fanwang <[email protected]>
Restructure ActiveToken so GH_USER overrides the stored active account's config
or keyring token (an environment GH_TOKEN/GITHUB_TOKEN still wins), resolving the
selected account per host from the keyring. When GH_USER is the active user and
has no keyring entry, fall through to normal resolution (config or legacy unkeyed
token); when it names a different account with no token, return nothing rather
than another account's token — so it never leaks across accounts or hosts.

Extends coverage to 14 tests: multi-host resolution and cross-host no-leak,
same-username-per-host, mixed secure/insecure storage, three-account selection,
empty-string GH_USER, HasActiveToken, and the legacy-keyring fallback.

Signed-off-by: 1fanwang <[email protected]>
@williammartin

Copy link
Copy Markdown
Member

Hey @1fanwang, I'm happy to let you know that I'm finally getting round to giving this feature some further report. You'll find a bunch of previously closed PRs for the same idea:

I think there's a few more I can't find. The main issue with the approach outlined here (with no alternative approach) is that while it works for gh operations when called directly, but for users expecting git to work (either via gh auth setup-git or gh auth switch informing another credential helper) based on the token gh has obtained, there's some sharp edges ensuring that user choice flows to git.

There's also these that are loosely related to the same problem:

So I appreciate your trying to help out here (and reading the contribution guide re: help wanted), but I'm going to close this for the same reason the others are closed with the hope we can get something for the community not too far away. We absolutely know this is a pain point, there's just so much work for us to get through with the huge increase in gh usage due to agents. We have grown the team so 🤞

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external pull request originating outside of the CLI core team unmet-requirements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants