feat(github): bind projects to a specific gh account (#13664)

* feat(github): bind projects to a specific gh account

Adds per-project `Repo.ghAccount` so repo-scoped gh calls (create-worktree
issue/PR search, work items, hosted-review reads and mutations) run as the bound
account via ephemeral child-env token injection instead of the globally active
gh login. Multi-account resolution is capability-gated (gh >= 2.40) and fails
closed when the bound account or host is unavailable; Project View stays
ambient by design.

Repository settings gains a section for selecting or clearing a keyring-backed
account (shadcn `Select`), with mixed-version "not enforced" handling for older
remote runtimes. Attached `-Rhost/owner/repo` forms are covered by the host-drift
guard and its tests; es/ja/ko/zh catalogs carry the section's strings.

`getLocalProjectGhExecOptions` centralizes the binding lookup so every gh
execution path picks it up, including the Electron `hostedReview:*` handlers
that previously stayed on the ambient login. `gh auth token` (a keyring read)
is exempt from the rate-limit breaker gate so a tripped bucket cannot turn a
bound-token resolve into a false "unavailable".

The `ghAccount` update field and the two binding RPC methods live in the shared
RPC params contract; the generated catalog is regenerated.

Fixes #13612

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012B3QEP5iP4WGGEpPLtkHqA

* fix(settings): make GitHub account refresh secondary

* fix(github): satisfy strict casting quality checks

* test(rpc): use runtime fixture for repo binding

* fix(github): preserve project account for PR worktree lookups

* test(rpc): avoid incomplete runtime settings fixture

* fix(i18n): add GitHub account refresh label

* fix(i18n): refresh runtime required catalog

* fix(windows): preserve mobile patch bytes

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
Leo Levintza
2026-09-18 20:39:52 -07:00
committed by GitHub
co-authored by Claude Fable 5.1 Neil
parent 0817476b2c
commit c34b944136
77 changed files with 2780 additions and 62 deletions
@@ -9,7 +9,7 @@ import { resolveGitHubReviewHeadRemote } from '../github/review-head-remote'
import { gitExecFileAsync } from '../git/runner'
import {
getLocalProjectGitExecOptions,
getLocalProjectWorktreeGitOptions
getLocalProjectGhExecOptions
} from '../project-runtime-git-options'
import { requireSshGitProvider } from '../providers/ssh-git-dispatch'
import { isFolderRepo } from '../../shared/repo-kind'
@@ -43,9 +43,11 @@ export async function resolveRuntimeGitHubWorktreeBase(
const localGitExecOptions = sshGitProvider
? undefined
: getLocalProjectGitExecOptions(deps.store, repo)
const localWorktreeGitOptions = sshGitProvider
? {}
: getLocalProjectWorktreeGitOptions(deps.store, repo)
const localGhOptions = sshGitProvider
? repo.ghAccount
? { ghAccount: repo.ghAccount }
: undefined
: getLocalProjectGhExecOptions(deps.store, repo)
const gitExec = sshGitProvider
? (gitArgs: string[]) => sshGitProvider.exec(gitArgs, repo.path)
: (gitArgs: string[]) => gitExecFileAsync(gitArgs, localGitExecOptions ?? { cwd: repo.path })
@@ -54,7 +56,7 @@ export async function resolveRuntimeGitHubWorktreeBase(
repoPath: repo.path,
issueSourcePreference: repo.issueSourcePreference,
connectionId: repo.connectionId ?? null,
localGitOptions: localWorktreeGitOptions,
localGitOptions: localGhOptions,
gitExec
})
const fetchRemoteTrackingRef = (remote: string, branch: string): Promise<void> =>
@@ -81,7 +83,7 @@ export async function resolveRuntimeGitHubWorktreeBase(
isCrossRepository: args.isCrossRepository,
issueSourcePreference: repo.issueSourcePreference,
connectionId: repo.connectionId ?? null,
localGitOptions: localWorktreeGitOptions,
localGitOptions: localGhOptions,
gitExec,
fetchRemoteTrackingRef,
fetchPullRequestHeadRef,