What's wrong
When Azure DevOps rejects a credential with a 401, the client throws Microsoft.VisualStudio.Services.Common.VssUnauthorizedException. That type derives from VssException, not from VssServiceException. The provider only catches the latter, so this exception gets past both of its handlers:
EnsureAzureDevOpsClients (BuildMonitor/Providers/AzureDevOps.cs ~L87–100) catches only VssServiceException and UriFormatException. Sessions.Get(...) → CreateSession → connection.GetClient<ProjectHttpClient>() authenticates synchronously, so with a bad PAT it throws VssUnauthorizedException, and that exception escapes EnsureAzureDevOpsClients.
- The 401 handler in
MakeAzureDevOpsRequestAsync (~L414), catch (VssServiceResponseException ex) when (ex.HttpStatusCode == Unauthorized), never sees the 401 in this form. OnAuthenticationFailure() is therefore never called.
The remarks in BuildMonitor.Test/AzureDevOpsSessionTests.cs already note that GetClient "throws VssUnauthorizedException" offline, but the provider does not handle it.
Failure scenario
- The user's ADO PAT expires or is revoked, or they paste a wrong one.
UpdateRepositoriesAsync (and UpdateBuildsAsync / UpdateBuildAsync / UpdateRunAsync) throws out of EnsureAzureDevOpsClients. UpdateAsync faults inside its Task.WhenAll and logs "Update loop failed".
ProviderRefreshTimer.Restart() and the UpdateBuildsAsync() / UpdateRunsAsync() calls after it are never reached. OnRender starts UpdateAsync again straight away, so the loop runs back-to-back instead of on its interval:
- discovery re-runs for every provider on every cycle, which burns GitHub rate limit
- GitHub build and run polling never runs at all
- a fresh ADO auth attempt is made on every cycle
- The token is never cleared and the status bar never shows AuthFailed. This lasts until the user notices and fixes the PAT by hand.
How it was verified
- Built against the pinned
Microsoft.VisualStudio.Services.Client / Microsoft.TeamFoundationServer.Client 19.225.2.
typeof(VssUnauthorizedException).BaseType == typeof(VssException), and VssServiceException is not assignable from it.
new VssConnection(new Uri("https://dev.azure.com/<org>"), new VssBasicCredential("", "bogus")).GetClient<ProjectHttpClient>() throws VssUnauthorizedException: VS30063: You are not authorized… synchronously.
Related
Suggested fix
- Catch
VssUnauthorizedException in EnsureAzureDevOpsClients and in MakeAzureDevOpsRequestAsync, and route it to OnAuthenticationFailure() (and to SetStatus(AuthFailed, …) in the first case).
- Consider also catching
HttpRequestException in EnsureAzureDevOpsClients. Being offline at startup escapes the same way, though that failure is only transient.
Acceptance criteria
- A test with a session factory that throws
VssUnauthorizedException shows that EnsureAzureDevOpsClients returns null, the provider status becomes AuthFailed, and the credentials are cleared.
- With a bad ADO PAT configured, GitHub builds still poll on their normal interval.
What's wrong
When Azure DevOps rejects a credential with a 401, the client throws
Microsoft.VisualStudio.Services.Common.VssUnauthorizedException. That type derives fromVssException, not fromVssServiceException. The provider only catches the latter, so this exception gets past both of its handlers:EnsureAzureDevOpsClients(BuildMonitor/Providers/AzureDevOps.cs~L87–100) catches onlyVssServiceExceptionandUriFormatException.Sessions.Get(...)→CreateSession→connection.GetClient<ProjectHttpClient>()authenticates synchronously, so with a bad PAT it throwsVssUnauthorizedException, and that exception escapesEnsureAzureDevOpsClients.MakeAzureDevOpsRequestAsync(~L414),catch (VssServiceResponseException ex) when (ex.HttpStatusCode == Unauthorized), never sees the 401 in this form.OnAuthenticationFailure()is therefore never called.The remarks in
BuildMonitor.Test/AzureDevOpsSessionTests.csalready note thatGetClient"throwsVssUnauthorizedException" offline, but the provider does not handle it.Failure scenario
UpdateRepositoriesAsync(andUpdateBuildsAsync/UpdateBuildAsync/UpdateRunAsync) throws out ofEnsureAzureDevOpsClients.UpdateAsyncfaults inside itsTask.WhenAlland logs "Update loop failed".ProviderRefreshTimer.Restart()and theUpdateBuildsAsync()/UpdateRunsAsync()calls after it are never reached.OnRenderstartsUpdateAsyncagain straight away, so the loop runs back-to-back instead of on its interval:How it was verified
Microsoft.VisualStudio.Services.Client/Microsoft.TeamFoundationServer.Client19.225.2.typeof(VssUnauthorizedException).BaseType == typeof(VssException), andVssServiceExceptionis not assignable from it.new VssConnection(new Uri("https://dev.azure.com/<org>"), new VssBasicCredential("", "bogus")).GetClient<ProjectHttpClient>()throwsVssUnauthorizedException: VS30063: You are not authorized…synchronously.Related
UpdateAsyncthere would limit the damage, but it still would not report the bad PAT as an auth failure.ObjectDisposedExceptionafter credentials change).Suggested fix
VssUnauthorizedExceptioninEnsureAzureDevOpsClientsand inMakeAzureDevOpsRequestAsync, and route it toOnAuthenticationFailure()(and toSetStatus(AuthFailed, …)in the first case).HttpRequestExceptioninEnsureAzureDevOpsClients. Being offline at startup escapes the same way, though that failure is only transient.Acceptance criteria
VssUnauthorizedExceptionshows thatEnsureAzureDevOpsClientsreturnsnull, the provider status becomes AuthFailed, and the credentials are cleared.