What's wrong
The hosting layer converts API errors into GitHostingException subclasses, but transport failures from HttpClient are never translated:
Hosting/GitHubDeviceFlow.cs:209 and :318: the XML docs at :194 and :280 say GitHostingRequestException is thrown when "GitHub … could not be reached". In practice HttpRequestException propagates.
Hosting/AzureDevOpsProvider.cs:142, :205, :273: SendAsync is not wrapped.
Hosting/GitHubProvider.cs:101, :227, :263: only Octokit's ApiException is caught.
Nothing in GitIntegration/ handles HttpRequestException.
Repro
Inject a Handler whose SendAsync throws new HttpRequestException("No such host is known."):
| Call |
Thrown |
GitHubDeviceFlow.RequestDeviceCodeAsync() |
System.Net.Http.HttpRequestException |
new GitHubProvider { Owner = "contoso", … }.GetRepositoriesAsync() |
System.Net.Http.HttpRequestException |
new AzureDevOpsProvider { Owner = "contoso", … }.GetRepositoriesAsync() |
System.Net.Http.HttpRequestException |
Why it matters
A caller that writes catch (GitHostingException), which is what the docs and CLAUDE.md's "the failure surface is the GitHostingException hierarchy" rule tell them to do, crashes when the machine is offline, DNS fails, or a proxy breaks TLS. The same rule is already enforced for JsonException and ArgumentException. #106 (closed) fixed the same class of leak for NotSupportedException.
Suggested fix
- Wrap each
SendAsync and Octokit call. Translate HttpRequestException into GitHostingRequestException(message, inner).
- Treat a
TaskCanceledException / OperationCanceledException whose token is not the caller's (an HttpClient.Timeout) the same way. Let genuine caller cancellation through unchanged.
Acceptance criteria
- Fake-handler tests for each provider method and each device-flow call assert that
GitHostingRequestException is thrown, with the transport exception as InnerException.
- A test asserts that caller-token cancellation still surfaces as
OperationCanceledException.
What's wrong
The hosting layer converts API errors into
GitHostingExceptionsubclasses, but transport failures fromHttpClientare never translated:Hosting/GitHubDeviceFlow.cs:209and:318: the XML docs at:194and:280sayGitHostingRequestExceptionis thrown when "GitHub … could not be reached". In practiceHttpRequestExceptionpropagates.Hosting/AzureDevOpsProvider.cs:142,:205,:273:SendAsyncis not wrapped.Hosting/GitHubProvider.cs:101,:227,:263: only Octokit'sApiExceptionis caught.Nothing in
GitIntegration/handlesHttpRequestException.Repro
Inject a
HandlerwhoseSendAsyncthrowsnew HttpRequestException("No such host is known."):GitHubDeviceFlow.RequestDeviceCodeAsync()System.Net.Http.HttpRequestExceptionnew GitHubProvider { Owner = "contoso", … }.GetRepositoriesAsync()System.Net.Http.HttpRequestExceptionnew AzureDevOpsProvider { Owner = "contoso", … }.GetRepositoriesAsync()System.Net.Http.HttpRequestExceptionWhy it matters
A caller that writes
catch (GitHostingException), which is what the docs and CLAUDE.md's "the failure surface is the GitHostingException hierarchy" rule tell them to do, crashes when the machine is offline, DNS fails, or a proxy breaks TLS. The same rule is already enforced forJsonExceptionandArgumentException. #106 (closed) fixed the same class of leak forNotSupportedException.Suggested fix
SendAsyncand Octokit call. TranslateHttpRequestExceptionintoGitHostingRequestException(message, inner).TaskCanceledException/OperationCanceledExceptionwhose token is not the caller's (anHttpClient.Timeout) the same way. Let genuine caller cancellation through unchanged.Acceptance criteria
GitHostingRequestExceptionis thrown, with the transport exception asInnerException.OperationCanceledException.