Skip to content

An invalid or expired Azure DevOps PAT is never reported as AuthFailed and stalls the whole update loop, GitHub polling included #305

Description

@matt-edmondson

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

  1. The user's ADO PAT expires or is revoked, or they paste a wrong one.
  2. UpdateRepositoriesAsync (and UpdateBuildsAsync / UpdateBuildAsync / UpdateRunAsync) throws out of EnsureAzureDevOpsClients. UpdateAsync faults inside its Task.WhenAll and logs "Update loop failed".
  3. 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
  4. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingreadyFully specified; implement as written

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions