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
{{ message }}
Repository navigation
AzureDevOpsProvider reports an expired or missing PAT as GitHostingRequestException with an HTML redirect page as its message #176
GitProvider.CreateDefaultHandler sets AllowAutoRedirect = false (GitIntegration/GitProvider.cs ~L510). When a REST call has bad credentials or none at all, Azure DevOps does not answer 401. It answers with a 302 redirect to a sign-in page, unless the request sends a header asking it not to.
The following was checked live against dev.azure.com:
The same two requests with X-TFS-FedAuthRedirect: Suppress return HTTP/2 401 with application/json and body {"message":"TF400813: The user '...' is not authorized to access this resource.", ...}.
Here is how the 302 flows through the provider (Hosting/AzureDevOpsProvider.cs):
GetRepositoriesAsync, GetPullRequestsCoreAsync and CreatePullRequestCoreAsync see a non-success status and call Translate.
Status 302 matches none of the 429/401/403/404 arms in Translate, so it falls through to new GitHostingRequestException(...).
ExtractMessage cannot parse the HTML as JSON, so the exception message is the raw HTML page.
As a result, a caller's catch (GitHostingAuthenticationException) never fires for the most common Azure DevOps auth failure, an expired PAT. On GitHub the equivalent failure does map to GitHostingAuthenticationException. CLAUDE.md says auth failures map to that exception on both hosts.
Repro
Call GetRepositoriesAsync() on an AzureDevOpsProvider for a private org, using a persona whose PAT has expired. It throws GitHostingRequestException, and its Message starts with <html><head><title>Object moved</title>.
Suggested fix
Send X-TFS-FedAuthRedirect: Suppress on every Azure DevOps request, for example in ApplyAuthentication. The service then answers 401 with a JSON message, which ExtractMessage already handles.
As defence in depth, have Translate map two more cases to GitHostingAuthenticationException:
a 3xx whose Location points at a sign-in endpoint (_signin)
a 203 Non-Authoritative response, which otherwise reaches DeserializeSuccessBody
Optional: when the error body is not JSON, use a short summary as the exception message (status, reason phrase and content type) and keep the full text only on ResponseBody. Azure DevOps' 404 for an unknown organisation is about 133 KB of HTML.
Acceptance criteria
A FakeHttpMessageHandler test returns a 302 with Location: .../_signin?... and an HTML body, and the provider throws GitHostingAuthenticationException.
A test asserts that the suppress header is sent on the repositories, list-PRs and create-PR request paths.
Priority: High. An expired PAT is the most common Azure DevOps failure. It currently skips the documented GitHostingAuthenticationException and surfaces as a generic exception whose message is an HTML page. Callers can't detect it and prompt for a new token.
Duplicates / in progress: Not a duplicate, and no open PR covers it.
Next step: Send X-TFS-FedAuthRedirect: Suppress on every Azure DevOps request. As a fallback, map a 3xx redirect to _signin, and a 203 response, to GitHostingAuthenticationException. Add FakeHttpMessageHandler tests for the redirect and for the header on all three request paths.
Activity
matt-edmondson commentedon Oct 6, 2026
Triage
GitHostingAuthenticationExceptionand surfaces as a generic exception whose message is an HTML page. Callers can't detect it and prompt for a new token.GitIntegration/Hosting/AzureDevOpsProvider.cs(ApplyAuthentication,Translate,ExtractMessage)X-TFS-FedAuthRedirect: Suppresson every Azure DevOps request. As a fallback, map a 3xx redirect to_signin, and a 203 response, toGitHostingAuthenticationException. AddFakeHttpMessageHandlertests for the redirect and for the header on all three request paths.Generated by Claude Code