fix: scan each owner with its own credentials, not the previous owner's [patch] - #429
Merged
Merged
Conversation
…'s [patch] GitHubClient.Credentials is one mutable property on a client shared by every owner in the scan, and ScanRemoteAccountsForRepos only assigned it when the owner had a PAT or a global login existed. An owner with neither left the previous owner's credentials in place and was scanned as them. The concrete case: a personal PAT is configured for owner A, and owner B is added with no credentials to browse publicly. B is then scanned as A, so B's own private repositories are missed, and anything that genuinely needed B's auth answers with an ApiException that the caller swallows -- leaving the repositories missing with no indication why. Pull the choice out into ChooseCredentials, which is total: the owner's own token, else the global login, else Credentials.Anonymous. The caller assigns its result for every owner, so nothing can carry over. A global login missing its token, or a token missing its login, now falls through to anonymous rather than reaching Octokit, which rejects an empty half. Fixes #426 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EdV5iCFkUxFqkLZQAGLAVT
The SonarCloud gate failed at 75% coverage on new code, against a required 80%. The uncovered line was the one that mattered most: the assignment in ScanRemoteAccountsForRepos, which is what actually stops the previous owner's identity carrying over. ChooseCredentials was fully covered, but the rule only helps if the caller applies it for every owner, and nothing exercised that. Move the assignment into ApplyCredentials so a test can watch one client across two owners -- the shape the defect actually had. Scanning A with a PAT and then B with nothing now asserts against a real GitHubClient that B is anonymous rather than still authenticated as A. Verified by restoring the conditional assignment in ApplyCredentials: ScanningASecondOwnerDoesNotInheritTheFirstOwnersCredentials fails against it and passes against the fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EdV5iCFkUxFqkLZQAGLAVT
|
matt-edmondson
pushed a commit
that referenced
this pull request
Sep 22, 2026
Brings in #429 and #430, both of which changed ProjectDirector.cs, so the combination is built and tested here rather than assumed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EdV5iCFkUxFqkLZQAGLAVT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Fixes #426
What was wrong
GitHubClient.Credentialsis one mutable property on a client shared by every owner in the scan.ScanRemoteAccountsForReposonly assigned it when the owner had a PAT, or when a global login existed:An owner matching neither branch does not get scanned anonymously — it gets scanned as whoever was set last.
The concrete case from the issue: a personal PAT is configured for owner A, and owner B is added with no credentials, just to browse publicly. B's scan runs as A. B's own private repositories are missed, and anything that genuinely needs B's auth answers with an
ApiExceptionthat the caller swallows at lines 777-780 — so the repositories are simply missing, with no indication why.The fix
ChooseCredentialsmakes the answer total, which is the property the old code lacked:Credentials(owner, pat)Credentials(login, token)Credentials.AnonymousThe caller now assigns its result for every owner, so nothing can carry over.
The third row also closes a smaller gap: a global login missing its token, or a token missing its login, used to be excluded by the guard and so silently left the previous credentials in place. It now falls through to anonymous rather than reaching Octokit, which rejects an empty half.
Tests
ProjectDirector.Test/ScanCredentialTests.cs, following the pattern CLAUDE.md sets out — the part with a rule in it is a plain method, drivable without a live ImGui context or a GitHub account.AnOwnerWithNoCredentialsAnywhereIsScannedAnonymouslyis the regression itself: it asks for owner A with a PAT and then owner B with nothing, and asserts B comes back anonymous and specifically not asalpha.Verified by substituting the original behaviour back in (returning the last-set credentials for an owner that matched neither branch) and re-running:
AnOwnerWithNoCredentialsAnywhereIsScannedAnonymously,AGlobalLoginMissingItsTokenIsNotUsedandAGlobalTokenMissingItsLoginIsNotUsedall fail against it and pass against the fix.Full suite on this branch: 45 total, 43 passed, 0 failed, 2 inconclusive (both need git-lfs, unrelated and present on
main).dotnet build --configuration Releaseis clean.Scope
The constructor's own one-time
GitHubClient.Credentialsassignment (lines 93-96) is left alone — a freshly constructed client is anonymous, so there is no stale identity for it to leak.🤖 Generated with Claude Code
https://claude.ai/code/session_01EdV5iCFkUxFqkLZQAGLAVT
Generated by Claude Code