Skip to content

Add owner-based identity selection - #14923

Draft
williammartin wants to merge 2 commits into
williammartin-multiaccount-draft-03-directoriesfrom
williammartin-multiaccount-draft-04-owners
Draft

williammartin wants to merge 2 commits into
williammartin-multiaccount-draft-03-directoriesfrom
williammartin-multiaccount-draft-04-owners

Conversation

@williammartin

@williammartin williammartin commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

Prototype overview and documentation

DO NOT MERGE. This stack is almost entirely vibe coded. These drafts are for handy review and prototyping, not merging as-is.

Expect the code to be really bad in places: highly coupled, handling edge cases that do not really matter, or doing suspiciously fragile things like parsing GraphQL request bodies.

I suspect some of the suspicious code also exposes the complexity of this feature set, because account selection, Git configuration, scope precedence, worktrees, SSH and API requests all interact.

The prototype experience is the purpose, not the code. I want to find out whether the experience is useful, not defend this implementation or treat it as a design to preserve.

Description

Follow on from #14922

A repository owner can be a more useful account-selection rule than where the repository happens to be cloned. This PR adds owner bindings as a fallback behind repository and directory choices.

Layer 4 of an eight-part prototype review stack. This is a way to review the design and implementation in smaller pieces, not a proposal to merge the current stack as-is.

How did you test this change?

I inspected both pages of the actual owner binding file and checked the selected real account. I exercised owner fallback, stronger directory and repository choices, and refusal when remotes imply conflicting bound owners.

$ git config --get remote.origin.url
https://github.com/williammartin/adventofcode.git

An owner binding matches repository remote URLs

multiaccount-layer-4-01-Wiring---Layer-4---Owner-binding-file.mp4

A repository owner provides a fallback, independent of the selected account login

multiaccount-layer-4-02-A-repository-owner-provides-a-fallback-independent-of-the-selected-account-login.mp4

Conflicting remote-owner choices refuse to guess; an explicit repository binding resolves the ambiguity

multiaccount-layer-4-03-Conflicting-remote-owner-choices-refuse-to-guess-an-explicit-repository-binding-resolves-the-ambiguity.mp4

Directory and repository choices outrank owner fallback, and removal reveals the next surviving choice

multiaccount-layer-4-04-Directory-and-repository-choices-outrank-owner-fallback-and-removal-reveals-the-next-surviving-choice.mp4

Key points

Native includeIf conditions use hasconfig:remote.*.url to load the identity for matching remotes. Separate patterns cover HTTPS and SSH URL forms without rewriting the saved remote.

Character classes such as [gG] and [wW] are intentional. Git matches these URL globs case-sensitively, but GitHub hostnames and owner names are case-insensitive. The generated patterns therefore match mixed-case remotes without changing their spelling.

Notes for reviewers

Start with owner extraction and precedence, then compare API request context with the native Git conditions. Requests involving competing owner choices fail rather than guess. Owner selection does not override an explicit repository or directory choice.

The recordings use the product code at this layer, not a later stack endpoint. The included offline replay scripts are supplementary and do not reproduce the live-account recordings.

Offline replay on macOS

Build this layer, then run its synthetic scenario with an absolute executable path. This is a replay instruction, not a claim that the command below was used to make the videos.

make
env -i HOME="$HOME" PATH=/usr/bin:/bin:/opt/homebrew/bin \
  /bin/bash script/multiaccount-review/04-owners.sh --gh "$PWD/bin/gh"

Authorship and follow-up

Who wrote this:

  • A human wrote it.
  • An agent wrote it under close human direction.
  • An agent wrote it independently, and no human has guided the implementation beyond the initial prompt.

Who answers review comments:

  • @username will read and reply directly. Name the account.
  • An agent will draft replies and @username will read them before they are posted.
  • Nobody has explicitly committed to replying.

Acceptance Criteria

Prototype behaviour under review:

Given an owner is bound to an identity and no stronger scope applies.

When a repository remote or supported API request identifies that owner.

Then the owner binding selects the identity without changing the remote URL.

DO NOT MERGE

williammartin and others added 2 commits November 3, 2026 22:11
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 965836ef-19ac-4473-ba1f-e326ce0f106b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 965836ef-19ac-4473-ba1f-e326ce0f106b
Copilot AI balanced review requested due to automatic review settings November 3, 2026 21:12

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.

🟡 Changes recommended

Unresolved identity-resolution defects break scope precedence and destination-specific Git credentials, while GraphQL handling rejects valid block strings and ignores owner selection from file or stdin input.

4 open issues
What changed in this PR

This draft adds owner-based identity selection to GitHub CLI’s multi-account prototype, extending the preceding directory-binding layer with an owner fallback below repository and directory choices. It remains explicitly marked DO NOT MERGE.

Changes:

  • Add owner binding storage, command flags, cleanup reporting, and native Git include conditions.
  • Pass API request context and Git credential paths into account selection.
  • Extend tests and add an offline replay for owner precedence and ambiguity.
File Description
script/​multiaccount-review/​04-owners.sh Replay owner precedence and conflicting-owner scenarios.
pkg/​cmd/​repo/​create/​identity_test.go Verify directory identity during ownerless repository creation.
pkg/​cmd/​auth/​use/​use.go Add owner binding and inspection options.
pkg/​cmd/​auth/​use/​use_test.go Test scope storage and flag validation.
pkg/​cmd/​auth/​identity/​delete.go Include owner bindings in deletion descriptions.
pkg/​cmd/​auth/​identity/​delete_test.go Cover owner cleanup and deletion warnings.
pkg/​cmd/​auth/​gitcredential/​identity_test.go Exercise owner-based HTTPS credentials.
pkg/​cmd/​auth/​gitcredential/​helper.go Forward repository paths to credential resolution.
pkg/​cmd/​auth/​gitcredential/​helper_test.go Adapt the credential test double.
internal/​identity/​store.go Store and resolve owner bindings; generate Git conditions.
internal/​identity/​store_test.go Cover precedence, capitalization, and owner conflicts.
internal/​identity/​ssh_test.go Exercise owner-based SSH routing.
internal/​identity/​request.go Extract owner context from REST and GraphQL requests.
internal/​identity/​request_test.go Test request-based selection and environment-token refusal.
internal/​identity/​hardening_test.go Cover stale-scope removal and Git version requirements.
internal/​identity/​credentials.go Pass HTTP requests into token resolution.
internal/​identity/​account_test.go Test owner-specific SSH routes for account overrides.
internal/​gh/​gh.go Extend authentication interfaces with request context.
internal/​config/​config.go Use request owners and credential paths for selection.
internal/​config/​active_account_test.go Update credential-resolution calls.
api/​identity_test.go Test owner selection for API and clone lookups.
api/​identity_gateway_test.go Test canonical-host credentials through the identity resolver.
api/​http_client.go Forward requests to token resolvers.
api/​auth_resolver_test.go Adapt tests to request-aware resolver signatures.

Review effort: Balanced

Comment on lines +42 to +43
if owner != "" && value != "" && !strings.EqualFold(value, owner) {
return errors.New("GraphQL request has multiple owners; split the request by owner")
}
var lex scanner.Scanner
lex.Init(strings.NewReader(query.Query))
lex.Mode = scanner.ScanIdents | scanner.ScanStrings
Comment on lines +621 to +622
condition := "hasconfig:remote.*.url:" + prefix + ownerPattern(binding.Owner) + "/**"
fmt.Fprintf(&config, "[includeIf %s]\n\tpath = %s\n", gitQuote(condition), gitQuote(path))
Comment on lines +25 to +26
if !strings.HasSuffix(req.URL.Path, "/graphql") || req.GetBody == nil {
return "", nil

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants