From f8e76216a97591f74598b404e292c165759749fb Mon Sep 17 00:00:00 2001 From: helen229 Date: Tue, 7 Jul 2026 11:34:26 -0700 Subject: [PATCH 1/7] Don't require GitHub auth for public-repo PR status/label reads (#16210) --- .../Services/GitHubServiceTests.cs | 70 +++++++++++++++ .../Azure.Sdk.Tools.Cli/CHANGELOG.md | 2 + .../Services/GitHubService.cs | 87 ++++++++++++++++++- 3 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs diff --git a/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs b/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs new file mode 100644 index 00000000000..92f62495562 --- /dev/null +++ b/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using Azure.Sdk.Tools.Cli.Helpers; +using Azure.Sdk.Tools.Cli.Services; +using Moq; +using Octokit; + +namespace Azure.Sdk.Tools.Cli.Tests.Services; + +[TestFixture] +public class GitHubServiceTests +{ + private string? originalGitHubToken; + private string? originalGitHubPat; + + // Exposes the protected read-only client selection so it can be exercised in isolation. + private sealed class TestableGitConnection : GitConnection + { + public TestableGitConnection(IProcessHelper processHelper) : base(processHelper) { } + public GitHubClient GetReadOnlyClientForTest() => GetReadOnlyClient(); + } + + [SetUp] + public void SetUp() + { + // Preserve and clear the auth environment variables so tests control token availability. + originalGitHubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"); + originalGitHubPat = Environment.GetEnvironmentVariable("GITHUB_PERSONAL_ACCESS_TOKEN"); + Environment.SetEnvironmentVariable("GITHUB_TOKEN", null, EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable("GITHUB_PERSONAL_ACCESS_TOKEN", null, EnvironmentVariableTarget.Process); + } + + [TearDown] + public void TearDown() + { + Environment.SetEnvironmentVariable("GITHUB_TOKEN", originalGitHubToken, EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable("GITHUB_PERSONAL_ACCESS_TOKEN", originalGitHubPat, EnvironmentVariableTarget.Process); + } + + [Test] + public void GetReadOnlyClient_returns_anonymous_client_when_no_auth_token_available() + { + // Simulate `gh auth token` failing (user not authenticated). + var processHelperMock = new Mock(); + processHelperMock + .Setup(x => x.Run(It.IsAny(), It.IsAny())) + .ReturnsAsync(new ProcessResult { ExitCode = 1 }); + + var connection = new TestableGitConnection(processHelperMock.Object); + + var client = connection.GetReadOnlyClientForTest(); + + Assert.That(client.Credentials.AuthenticationType, Is.EqualTo(AuthenticationType.Anonymous)); + } + + [Test] + public void GetReadOnlyClient_returns_authenticated_client_when_token_available() + { + Environment.SetEnvironmentVariable("GITHUB_TOKEN", "fake-token", EnvironmentVariableTarget.Process); + var processHelperMock = new Mock(); + + var connection = new TestableGitConnection(processHelperMock.Object); + + var client = connection.GetReadOnlyClientForTest(); + + Assert.That(client.Credentials.AuthenticationType, Is.EqualTo(AuthenticationType.Bearer)); + // `gh auth token` should not be invoked when a token is already present in the environment. + processHelperMock.Verify(x => x.Run(It.IsAny(), It.IsAny()), Times.Never); + } +} diff --git a/tools/azsdk-cli/Azure.Sdk.Tools.Cli/CHANGELOG.md b/tools/azsdk-cli/Azure.Sdk.Tools.Cli/CHANGELOG.md index b19a724701c..3eb9b917ce3 100644 --- a/tools/azsdk-cli/Azure.Sdk.Tools.Cli/CHANGELOG.md +++ b/tools/azsdk-cli/Azure.Sdk.Tools.Cli/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bugs Fixed +- Reading the status, checks, and labels of a public pull request (for example, the spec pull request during SDK generation) no longer requires GitHub authentication. These read-only operations now fall back to an unauthenticated GitHub client when no token is available, so users are not prompted to run `gh auth login` for public-repository reads. + - The create release plan tool no longer accepts an `--sdk-type` parameter. The SDK release type is now always derived from the API release type (GA maps to a stable SDK release, preview maps to a beta SDK release), preventing a stable SDK release from a preview API version. ## 0.6.24 (2026-06-30) diff --git a/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs b/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs index 825fa0f7aab..3a991e8f4fa 100644 --- a/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs +++ b/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs @@ -23,6 +23,8 @@ public class GitConnection private const int GitHubCliAuthTokenTimeoutMs = 120000; private readonly IProcessHelper processHelper; private GitHubClient? _gitHubClient; // Backing field for the property + private GitHubClient? _anonymousGitHubClient; // Backing field for the anonymous client + private bool _authTokenUnavailable; // Cache negative auth lookups to avoid repeated `gh auth token` calls protected GitConnection(IProcessHelper processHelper) { @@ -45,6 +47,83 @@ public GitHubClient gitHubClient } } + /// + /// An unauthenticated GitHub client suitable for read-only access to public repositories. + /// + protected GitHubClient anonymousGitHubClient => + _anonymousGitHubClient ??= new GitHubClient(new ProductHeaderValue("AzureSDKDevToolsMCP")); + + /// + /// Returns an authenticated GitHub client when a token is available, otherwise an anonymous client + /// suitable for read-only access to public repositories. Unlike , this does + /// not throw when the user is not authenticated, so operations that only read public data (for example, + /// checking the status or labels of a public spec pull request during SDK generation) do not force the + /// user to run `gh auth login`. + /// + protected GitHubClient GetReadOnlyClient() + { + if (_gitHubClient != null) + { + return _gitHubClient; + } + + if (!_authTokenUnavailable && TryGetGitHubAuthToken(out var token)) + { + _gitHubClient = new GitHubClient(new ProductHeaderValue("AzureSDKDevToolsMCP")) + { + Credentials = new Credentials(token, AuthenticationType.Bearer) + }; + return _gitHubClient; + } + + _authTokenUnavailable = true; + return anonymousGitHubClient; + } + + /// + /// Attempts to resolve a GitHub auth token without throwing. Returns true and sets + /// when a token is available (from the GITHUB_TOKEN / GITHUB_PERSONAL_ACCESS_TOKEN environment variables or + /// the GitHub CLI); otherwise returns false. + /// + protected bool TryGetGitHubAuthToken(out string token) + { + token = Environment.GetEnvironmentVariable("GITHUB_TOKEN") ?? string.Empty; + if (!string.IsNullOrEmpty(token)) + { + return true; + } + + var patToken = Environment.GetEnvironmentVariable("GITHUB_PERSONAL_ACCESS_TOKEN"); + if (!string.IsNullOrEmpty(patToken)) + { + Environment.SetEnvironmentVariable("GITHUB_TOKEN", patToken, EnvironmentVariableTarget.Process); + token = patToken; + return true; + } + + try + { + var options = new ProcessOptions("gh", ["auth", "token"], timeout: TimeSpan.FromMilliseconds(GitHubCliAuthTokenTimeoutMs)); + using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMilliseconds(GitHubCliAuthTokenTimeoutMs)); + var result = processHelper.Run(options, timeoutCts.Token).GetAwaiter().GetResult(); + var ghToken = result.ExitCode == 0 ? result.Stdout.Trim() : string.Empty; + if (string.IsNullOrWhiteSpace(ghToken)) + { + token = string.Empty; + return false; + } + + Environment.SetEnvironmentVariable("GITHUB_TOKEN", ghToken, EnvironmentVariableTarget.Process); + token = ghToken; + return true; + } + catch + { + token = string.Empty; + return false; + } + } + protected string GetGitHubAuthToken() { var token = Environment.GetEnvironmentVariable("GITHUB_TOKEN"); @@ -149,7 +228,9 @@ public async Task GetGitUserDetailsAsync(CancellationToken ct) public async Task GetPullRequestAsync(string repoOwner, string repoName, int pullRequestNumber, CancellationToken ct) { - var pullRequest = await gitHubClient.PullRequest.Get(repoOwner, repoName, pullRequestNumber); + // Reading a pull request (including its status and labels) is a read-only operation that works + // anonymously for public repositories, so avoid forcing GitHub authentication here. + var pullRequest = await GetReadOnlyClient().PullRequest.Get(repoOwner, repoName, pullRequestNumber); return pullRequest; } @@ -189,7 +270,7 @@ public async Task CreateIssueAsync(string repoOwner, string repoName, str try { logger.LogInformation("Getting head SHA for PR #{PullRequestNumber} in {Owner}/{Repo}", pullRequestNumber, repoOwner, repoName); - var pr = await gitHubClient.PullRequest.Get(repoOwner, repoName, pullRequestNumber); + var pr = await GetReadOnlyClient().PullRequest.Get(repoOwner, repoName, pullRequestNumber); return pr?.Head?.Sha; } catch (Exception ex) @@ -494,7 +575,7 @@ public async Task> GetPullRequestChecksAsync(int pullRequestNumber, throw new NotFoundException($"Pull request {pullRequestNumber} not found.", System.Net.HttpStatusCode.NotFound); } - var checkResponse = await gitHubClient.Check.Run.GetAllForReference(repoOwner, repoName, pr.Head.Sha); + var checkResponse = await GetReadOnlyClient().Check.Run.GetAllForReference(repoOwner, repoName, pr.Head.Sha); if (checkResponse == null || checkResponse.TotalCount == 0) { logger.LogError("No checkruns found for pull request."); From 533c45834a06b84fdc2fe2ce27e41bcca21032a8 Mon Sep 17 00:00:00 2001 From: helen229 Date: Tue, 7 Jul 2026 11:54:03 -0700 Subject: [PATCH 2/7] Try anonymous GitHub read first, fall back to auth only when required (#16210) --- .../Services/GitHubServiceTests.cs | 60 ++++++++++---- .../Azure.Sdk.Tools.Cli/CHANGELOG.md | 2 +- .../Services/GitHubService.cs | 79 +++---------------- 3 files changed, 58 insertions(+), 83 deletions(-) diff --git a/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs b/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs index 92f62495562..18be014526d 100644 --- a/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs +++ b/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Net; using Azure.Sdk.Tools.Cli.Helpers; using Azure.Sdk.Tools.Cli.Services; using Moq; @@ -13,11 +14,12 @@ public class GitHubServiceTests private string? originalGitHubToken; private string? originalGitHubPat; - // Exposes the protected read-only client selection so it can be exercised in isolation. + // Exposes the protected anonymous-first read helper so it can be exercised in isolation. private sealed class TestableGitConnection : GitConnection { public TestableGitConnection(IProcessHelper processHelper) : base(processHelper) { } - public GitHubClient GetReadOnlyClientForTest() => GetReadOnlyClient(); + public Task ReadWithAnonymousFallbackForTest(Func> operation) + => ReadWithAnonymousFallbackAsync(operation, CancellationToken.None); } [SetUp] @@ -38,33 +40,59 @@ public void TearDown() } [Test] - public void GetReadOnlyClient_returns_anonymous_client_when_no_auth_token_available() + public async Task ReadWithAnonymousFallback_uses_anonymous_client_when_public_read_succeeds() { - // Simulate `gh auth token` failing (user not authenticated). var processHelperMock = new Mock(); - processHelperMock - .Setup(x => x.Run(It.IsAny(), It.IsAny())) - .ReturnsAsync(new ProcessResult { ExitCode = 1 }); - var connection = new TestableGitConnection(processHelperMock.Object); - var client = connection.GetReadOnlyClientForTest(); + var usedAuthType = await connection.ReadWithAnonymousFallbackForTest( + client => Task.FromResult(client.Credentials.AuthenticationType)); - Assert.That(client.Credentials.AuthenticationType, Is.EqualTo(AuthenticationType.Anonymous)); + Assert.That(usedAuthType, Is.EqualTo(AuthenticationType.Anonymous)); + // No authentication was attempted for a successful anonymous read. + processHelperMock.Verify(x => x.Run(It.IsAny(), It.IsAny()), Times.Never); } [Test] - public void GetReadOnlyClient_returns_authenticated_client_when_token_available() + public async Task ReadWithAnonymousFallback_retries_authenticated_when_anonymous_requires_auth() { + // A token is available in the environment, so the authenticated fallback can succeed. Environment.SetEnvironmentVariable("GITHUB_TOKEN", "fake-token", EnvironmentVariableTarget.Process); var processHelperMock = new Mock(); - var connection = new TestableGitConnection(processHelperMock.Object); - var client = connection.GetReadOnlyClientForTest(); + // Simulate GitHub hiding a private resource as Not Found for the anonymous client. + var usedAuthType = await connection.ReadWithAnonymousFallbackForTest(client => + { + if (client.Credentials.AuthenticationType == AuthenticationType.Anonymous) + { + throw new NotFoundException("Not Found", HttpStatusCode.NotFound); + } + return Task.FromResult(client.Credentials.AuthenticationType); + }); - Assert.That(client.Credentials.AuthenticationType, Is.EqualTo(AuthenticationType.Bearer)); - // `gh auth token` should not be invoked when a token is already present in the environment. - processHelperMock.Verify(x => x.Run(It.IsAny(), It.IsAny()), Times.Never); + Assert.That(usedAuthType, Is.EqualTo(AuthenticationType.Bearer)); + } + + [Test] + public void ReadWithAnonymousFallback_prompts_for_auth_when_required_and_no_token() + { + // No token in the environment and `gh auth token` fails, so the authenticated fallback must + // surface the standard authentication guidance instead of silently swallowing the failure. + var processHelperMock = new Mock(); + processHelperMock + .Setup(x => x.Run(It.IsAny(), It.IsAny())) + .ReturnsAsync(new ProcessResult { ExitCode = 1 }); + var connection = new TestableGitConnection(processHelperMock.Object); + + Assert.ThrowsAsync(async () => + await connection.ReadWithAnonymousFallbackForTest(client => + { + if (client.Credentials.AuthenticationType == AuthenticationType.Anonymous) + { + throw new NotFoundException("Not Found", HttpStatusCode.NotFound); + } + return Task.FromResult(0); + })); } } diff --git a/tools/azsdk-cli/Azure.Sdk.Tools.Cli/CHANGELOG.md b/tools/azsdk-cli/Azure.Sdk.Tools.Cli/CHANGELOG.md index 3eb9b917ce3..bf1a608388e 100644 --- a/tools/azsdk-cli/Azure.Sdk.Tools.Cli/CHANGELOG.md +++ b/tools/azsdk-cli/Azure.Sdk.Tools.Cli/CHANGELOG.md @@ -8,7 +8,7 @@ ### Bugs Fixed -- Reading the status, checks, and labels of a public pull request (for example, the spec pull request during SDK generation) no longer requires GitHub authentication. These read-only operations now fall back to an unauthenticated GitHub client when no token is available, so users are not prompted to run `gh auth login` for public-repository reads. +- Reading the status, checks, and labels of a public pull request (for example, the spec pull request during SDK generation) no longer requires GitHub authentication. These read-only operations are now attempted anonymously first, and only fall back to an authenticated request (prompting the user to run `gh auth login`) when GitHub indicates authentication is required, such as for a private repository. - The create release plan tool no longer accepts an `--sdk-type` parameter. The SDK release type is now always derived from the API release type (GA maps to a stable SDK release, preview maps to a beta SDK release), preventing a stable SDK release from a preview API version. diff --git a/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs b/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs index 3a991e8f4fa..a6d76cd8188 100644 --- a/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs +++ b/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs @@ -24,7 +24,6 @@ public class GitConnection private readonly IProcessHelper processHelper; private GitHubClient? _gitHubClient; // Backing field for the property private GitHubClient? _anonymousGitHubClient; // Backing field for the anonymous client - private bool _authTokenUnavailable; // Cache negative auth lookups to avoid repeated `gh auth token` calls protected GitConnection(IProcessHelper processHelper) { @@ -54,73 +53,21 @@ public GitHubClient gitHubClient _anonymousGitHubClient ??= new GitHubClient(new ProductHeaderValue("AzureSDKDevToolsMCP")); /// - /// Returns an authenticated GitHub client when a token is available, otherwise an anonymous client - /// suitable for read-only access to public repositories. Unlike , this does - /// not throw when the user is not authenticated, so operations that only read public data (for example, - /// checking the status or labels of a public spec pull request during SDK generation) do not force the - /// user to run `gh auth login`. + /// Runs a read-only GitHub operation anonymously first so that public data (for example, the status + /// or labels of a public spec pull request during SDK generation) can be read without authentication. + /// If the anonymous request fails (the resource is private, access is forbidden, or the rate limit is + /// exhausted), it is retried with an authenticated client, which prompts the user to run `gh auth login` + /// when no token is available. /// - protected GitHubClient GetReadOnlyClient() + protected async Task ReadWithAnonymousFallbackAsync(Func> operation, CancellationToken ct) { - if (_gitHubClient != null) - { - return _gitHubClient; - } - - if (!_authTokenUnavailable && TryGetGitHubAuthToken(out var token)) - { - _gitHubClient = new GitHubClient(new ProductHeaderValue("AzureSDKDevToolsMCP")) - { - Credentials = new Credentials(token, AuthenticationType.Bearer) - }; - return _gitHubClient; - } - - _authTokenUnavailable = true; - return anonymousGitHubClient; - } - - /// - /// Attempts to resolve a GitHub auth token without throwing. Returns true and sets - /// when a token is available (from the GITHUB_TOKEN / GITHUB_PERSONAL_ACCESS_TOKEN environment variables or - /// the GitHub CLI); otherwise returns false. - /// - protected bool TryGetGitHubAuthToken(out string token) - { - token = Environment.GetEnvironmentVariable("GITHUB_TOKEN") ?? string.Empty; - if (!string.IsNullOrEmpty(token)) - { - return true; - } - - var patToken = Environment.GetEnvironmentVariable("GITHUB_PERSONAL_ACCESS_TOKEN"); - if (!string.IsNullOrEmpty(patToken)) - { - Environment.SetEnvironmentVariable("GITHUB_TOKEN", patToken, EnvironmentVariableTarget.Process); - token = patToken; - return true; - } - try { - var options = new ProcessOptions("gh", ["auth", "token"], timeout: TimeSpan.FromMilliseconds(GitHubCliAuthTokenTimeoutMs)); - using var timeoutCts = new CancellationTokenSource(TimeSpan.FromMilliseconds(GitHubCliAuthTokenTimeoutMs)); - var result = processHelper.Run(options, timeoutCts.Token).GetAwaiter().GetResult(); - var ghToken = result.ExitCode == 0 ? result.Stdout.Trim() : string.Empty; - if (string.IsNullOrWhiteSpace(ghToken)) - { - token = string.Empty; - return false; - } - - Environment.SetEnvironmentVariable("GITHUB_TOKEN", ghToken, EnvironmentVariableTarget.Process); - token = ghToken; - return true; + return await operation(anonymousGitHubClient); } - catch + catch (Octokit.ApiException) { - token = string.Empty; - return false; + return await operation(gitHubClient); } } @@ -229,8 +176,8 @@ public async Task GetGitUserDetailsAsync(CancellationToken ct) public async Task GetPullRequestAsync(string repoOwner, string repoName, int pullRequestNumber, CancellationToken ct) { // Reading a pull request (including its status and labels) is a read-only operation that works - // anonymously for public repositories, so avoid forcing GitHub authentication here. - var pullRequest = await GetReadOnlyClient().PullRequest.Get(repoOwner, repoName, pullRequestNumber); + // anonymously for public repositories, so try anonymously first and only prompt for auth if needed. + var pullRequest = await ReadWithAnonymousFallbackAsync(client => client.PullRequest.Get(repoOwner, repoName, pullRequestNumber), ct); return pullRequest; } @@ -270,7 +217,7 @@ public async Task CreateIssueAsync(string repoOwner, string repoName, str try { logger.LogInformation("Getting head SHA for PR #{PullRequestNumber} in {Owner}/{Repo}", pullRequestNumber, repoOwner, repoName); - var pr = await GetReadOnlyClient().PullRequest.Get(repoOwner, repoName, pullRequestNumber); + var pr = await ReadWithAnonymousFallbackAsync(client => client.PullRequest.Get(repoOwner, repoName, pullRequestNumber), ct); return pr?.Head?.Sha; } catch (Exception ex) @@ -575,7 +522,7 @@ public async Task> GetPullRequestChecksAsync(int pullRequestNumber, throw new NotFoundException($"Pull request {pullRequestNumber} not found.", System.Net.HttpStatusCode.NotFound); } - var checkResponse = await GetReadOnlyClient().Check.Run.GetAllForReference(repoOwner, repoName, pr.Head.Sha); + var checkResponse = await ReadWithAnonymousFallbackAsync(client => client.Check.Run.GetAllForReference(repoOwner, repoName, pr.Head.Sha), ct); if (checkResponse == null || checkResponse.TotalCount == 0) { logger.LogError("No checkruns found for pull request."); From d61c133fc1021576b017dc9f5cb575ac7d010ad5 Mon Sep 17 00:00:00 2001 From: Helen Gao Date: Tue, 7 Jul 2026 14:36:56 -0700 Subject: [PATCH 3/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs b/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs index 18be014526d..5ffa009bfdf 100644 --- a/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs +++ b/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs @@ -75,7 +75,7 @@ public async Task ReadWithAnonymousFallback_retries_authenticated_when_anonymous } [Test] - public void ReadWithAnonymousFallback_prompts_for_auth_when_required_and_no_token() + public void ReadWithAnonymousFallback_PromptsForAuth_WhenRequiredAndNoToken() { // No token in the environment and `gh auth token` fails, so the authenticated fallback must // surface the standard authentication guidance instead of silently swallowing the failure. From 962a512b1df43e29fb00099759306908960fc63c Mon Sep 17 00:00:00 2001 From: Helen Gao Date: Tue, 7 Jul 2026 14:37:08 -0700 Subject: [PATCH 4/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs b/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs index 5ffa009bfdf..ace50b440a5 100644 --- a/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs +++ b/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs @@ -54,7 +54,7 @@ public async Task ReadWithAnonymousFallback_uses_anonymous_client_when_public_re } [Test] - public async Task ReadWithAnonymousFallback_retries_authenticated_when_anonymous_requires_auth() + public async Task ReadWithAnonymousFallback_RetriesAuthenticated_WhenAnonymousRequiresAuth() { // A token is available in the environment, so the authenticated fallback can succeed. Environment.SetEnvironmentVariable("GITHUB_TOKEN", "fake-token", EnvironmentVariableTarget.Process); From 49b58a9c22332f2f82a57c7ef638c7c4cae2a509 Mon Sep 17 00:00:00 2001 From: Helen Gao Date: Tue, 7 Jul 2026 14:37:46 -0700 Subject: [PATCH 5/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs b/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs index ace50b440a5..e84d2888597 100644 --- a/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs +++ b/tools/azsdk-cli/Azure.Sdk.Tools.Cli.Tests/Services/GitHubServiceTests.cs @@ -40,7 +40,7 @@ public void TearDown() } [Test] - public async Task ReadWithAnonymousFallback_uses_anonymous_client_when_public_read_succeeds() + public async Task ReadWithAnonymousFallback_UsesAnonymousClient_WhenPublicReadSucceeds() { var processHelperMock = new Mock(); var connection = new TestableGitConnection(processHelperMock.Object); From 664043670f00ea8947ea2f14b71665f8b1cd35c8 Mon Sep 17 00:00:00 2001 From: Helen Gao Date: Tue, 7 Jul 2026 14:43:23 -0700 Subject: [PATCH 6/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Services/GitHubService.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs b/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs index a6d76cd8188..f47ae2d0816 100644 --- a/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs +++ b/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs @@ -59,17 +59,19 @@ public GitHubClient gitHubClient /// exhausted), it is retried with an authenticated client, which prompts the user to run `gh auth login` /// when no token is available. /// - protected async Task ReadWithAnonymousFallbackAsync(Func> operation, CancellationToken ct) - { - try - { - return await operation(anonymousGitHubClient); - } - catch (Octokit.ApiException) - { - return await operation(gitHubClient); - } - } +protected async Task ReadWithAnonymousFallbackAsync(Func> operation, CancellationToken ct) +{ + ct.ThrowIfCancellationRequested(); + try + { + return await operation(anonymousGitHubClient); + } + catch (Octokit.ApiException) + { + ct.ThrowIfCancellationRequested(); + return await operation(gitHubClient); + } +} protected string GetGitHubAuthToken() { From c5b667f7627032dadcb3c2d3f67109525febc2c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 8 Jul 2026 19:59:40 +0000 Subject: [PATCH 7/7] Fix indentation of ReadWithAnonymousFallbackAsync method in GitHubService.cs Co-authored-by: helen229 <19517014+helen229@users.noreply.github.com> --- .../Services/GitHubService.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs b/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs index f47ae2d0816..0fa09938342 100644 --- a/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs +++ b/tools/azsdk-cli/Azure.Sdk.Tools.Cli/Services/GitHubService.cs @@ -59,19 +59,19 @@ public GitHubClient gitHubClient /// exhausted), it is retried with an authenticated client, which prompts the user to run `gh auth login` /// when no token is available. /// -protected async Task ReadWithAnonymousFallbackAsync(Func> operation, CancellationToken ct) -{ - ct.ThrowIfCancellationRequested(); - try - { - return await operation(anonymousGitHubClient); - } - catch (Octokit.ApiException) - { - ct.ThrowIfCancellationRequested(); - return await operation(gitHubClient); - } -} + protected async Task ReadWithAnonymousFallbackAsync(Func> operation, CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + try + { + return await operation(anonymousGitHubClient); + } + catch (Octokit.ApiException) + { + ct.ThrowIfCancellationRequested(); + return await operation(gitHubClient); + } + } protected string GetGitHubAuthToken() {