You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Posting ahead of review per CONTRIBUTING's "Discuss Changes First" — happy to be redirected here before you spend time on the diff.
PR:#14373 · Issue:#14123 (partially addressed — see the end)
The bug
The background sweep batches every project's gh api graphql summary read into one document, keyed [host, credentialFingerprint, credentialScope] in GitHubPullRequestCli.ts. The sweep never pinned a credential, so the key collapsed to a single value for every project on the host — whichever workspace opened the batch decided the GitHub account for all of them. A user who separates accounts per directory loses PR status and the PR pane entirely.
The approach
PullRequestService.ts — summaryUncached wraps the read in the existing withVerifiedCredential, so each read runs under the credential its own workspace resolves.
GitHubPullRequestCli.ts — coalesce in-flight gh auth token reads per host\0cwd.
The batch key is not the bug, and I want to be precise about that because the issue's triage implies otherwise. The key already splits correctly the moment a credential is pinned — two credentials produce two documents. The defect is latent from #13198, which introduced the batching. What made it reproducible is the denser per-sweep reads that followed.
Coalescing, not a cache. A 10-minute token cache was the obvious answer and is wrong: three existing tests deliberately guarantee a gh auth switch is observed — one is named "reuses verified credentials offline and refuses an unverified replacement" — and a cache would keep a stale account for ten minutes, which is the same wrong-account symptom this fixes, self-inflicted. So only the in-flight read is shared; the entry is removed on settle, so any sequential read re-resolves. Staleness is bounded by the spawn's own duration instead of a TTL. Those three tests are unmodified and passing.
What I want feedback on
Two decisions a maintainer might reasonably make differently:
1. The coalescing might not belong in this PR. It's a performance follow-on to the fix, not part of it. The sweep runs ~25 reads a minute and would otherwise trade a batched read for a spawn per read. But if the preference is strictly one concern per PR, this splits cleanly into its own change and I'd do that.
2. The Effect.uninterruptible reasoning.Effect.cached runs the wrapped effect inline in the first reader's fiber, so an interrupted leader would publish an interrupt into the memo and fail peers that were never cancelled. The spawn is uninterruptible so peers get the value and only the cancelled reader loses. This is currently correct by construction but I'm adding a regression test for it — leader interrupted mid-spawn, peer still receives the token — rather than leaving it as an argument in a comment.
The issue reports two symptoms. This fixes the batched-summary one. Two things are untouched:
listStatsUncached (PullRequestService.ts:2443) has the same defect, on the PR-pane path. It groups by host and sends the chunk using first.project.project.workspaceRoot. It cannot take the same fix — wrapping that call in the first project's credential would still pin every project in the group to one account. Fixing it means changing the grouping key to carry the credential, which is a design question I didn't want to reopen here. If a pane error survives this change, that's where to look.
requireProject (PullRequestService.ts:845) is an independent second cause: a ref whose project does not own the repository still routes to an arbitrary non-Azure checkout on the host.
Because of the first, the PR body deliberately omits Fixes #14123.
Behavior changes worth knowing
Fail-closed. If gh auth token fails, the summary read now fails where it might previously have succeeded. That matches what routing() already does for the same reason, and it is the honest outcome — previously the server silently read whichever account the cwd happened to select.
Rate-limit bucketing now keys per credential rather than per host, so two accounts get two budgets. At most one extra cached quota probe per credential per 30s.
No contract, client-runtime, web, desktop, or mobile changes. withVerifiedCredential is optional on the provider API and only GitHub implements it; GitLab, Azure DevOps and Forgejo take the existing pass-through branch.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Posting ahead of review per CONTRIBUTING's "Discuss Changes First" — happy to be redirected here before you spend time on the diff.
PR: #14373 · Issue: #14123 (partially addressed — see the end)
The bug
The background sweep batches every project's
gh api graphqlsummary read into one document, keyed[host, credentialFingerprint, credentialScope]inGitHubPullRequestCli.ts. The sweep never pinned a credential, so the key collapsed to a single value for every project on the host — whichever workspace opened the batch decided the GitHub account for all of them. A user who separates accounts per directory loses PR status and the PR pane entirely.The approach
PullRequestService.ts—summaryUncachedwraps the read in the existingwithVerifiedCredential, so each read runs under the credential its own workspace resolves.GitHubPullRequestCli.ts— coalesce in-flightgh auth tokenreads perhost\0cwd.The batch key is not the bug, and I want to be precise about that because the issue's triage implies otherwise. The key already splits correctly the moment a credential is pinned — two credentials produce two documents. The defect is latent from #13198, which introduced the batching. What made it reproducible is the denser per-sweep reads that followed.
Coalescing, not a cache. A 10-minute token cache was the obvious answer and is wrong: three existing tests deliberately guarantee a
gh auth switchis observed — one is named "reuses verified credentials offline and refuses an unverified replacement" — and a cache would keep a stale account for ten minutes, which is the same wrong-account symptom this fixes, self-inflicted. So only the in-flight read is shared; the entry is removed on settle, so any sequential read re-resolves. Staleness is bounded by the spawn's own duration instead of a TTL. Those three tests are unmodified and passing.What I want feedback on
Two decisions a maintainer might reasonably make differently:
1. The coalescing might not belong in this PR. It's a performance follow-on to the fix, not part of it. The sweep runs ~25 reads a minute and would otherwise trade a batched read for a spawn per read. But if the preference is strictly one concern per PR, this splits cleanly into its own change and I'd do that.
2. The
Effect.uninterruptiblereasoning.Effect.cachedruns the wrapped effect inline in the first reader's fiber, so an interrupted leader would publish an interrupt into the memo and fail peers that were never cancelled. The spawn is uninterruptible so peers get the value and only the cancelled reader loses. This is currently correct by construction but I'm adding a regression test for it — leader interrupted mid-spawn, peer still receives the token — rather than leaving it as an argument in a comment.This deliberately does not close #14123
The issue reports two symptoms. This fixes the batched-summary one. Two things are untouched:
listStatsUncached(PullRequestService.ts:2443) has the same defect, on the PR-pane path. It groups by host and sends the chunk usingfirst.project.project.workspaceRoot. It cannot take the same fix — wrapping that call in the first project's credential would still pin every project in the group to one account. Fixing it means changing the grouping key to carry the credential, which is a design question I didn't want to reopen here. If a pane error survives this change, that's where to look.requireProject(PullRequestService.ts:845) is an independent second cause: a ref whose project does not own the repository still routes to an arbitrary non-Azure checkout on the host.Because of the first, the PR body deliberately omits
Fixes #14123.Behavior changes worth knowing
gh auth tokenfails, the summary read now fails where it might previously have succeeded. That matches whatrouting()already does for the same reason, and it is the honest outcome — previously the server silently read whichever account the cwd happened to select.No contract, client-runtime, web, desktop, or mobile changes.
withVerifiedCredentialis optional on the provider API and only GitHub implements it; GitLab, Azure DevOps and Forgejo take the existing pass-through branch.All reactions