{"record":{"id":"19cdbae37c356e0a","repo":"Devolutions/UniGetUI","slug":"github-request-failed-with-http-int-response-sta","errorCode":null,"errorMessage":"GitHub request failed with HTTP {(int)response.StatusCode} ({response.ReasonPhrase}): {GetErrorMessage(content)}","messagePattern":"GitHub request failed with HTTP (.+?) \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"HttpRequestException","httpStatus":null,"severity":"error","filePath":"src/UniGetUI.Interface.IpcApi/GitHubApiClient.cs","lineNumber":233,"sourceCode":"                    .Select(value => new KeyValuePair<string, string>(value.Key, value.Value!))\n            ),\n        };\n        message.Headers.Accept.Clear();\n        message.Headers.Accept.ParseAdd(\"application/json\");\n        return await SendAsync(message, typeInfo, cancellationToken);\n    }\n\n    private async Task<T> SendAsync<T>(\n        HttpRequestMessage message,\n        JsonTypeInfo<T> typeInfo,\n        CancellationToken cancellationToken\n    )\n    {\n        using HttpResponseMessage response = await _httpClient.SendAsync(message, cancellationToken);\n        string content = await response.Content.ReadAsStringAsync(cancellationToken);\n        if (!response.IsSuccessStatusCode)\n        {\n            throw new HttpRequestException(\n                $\"GitHub request failed with HTTP {(int)response.StatusCode} ({response.ReasonPhrase}): {GetErrorMessage(content)}\"\n            );\n        }\n\n        return JsonSerializer.Deserialize(content, typeInfo)\n            ?? throw new InvalidOperationException(\"GitHub returned an empty response.\");\n    }\n\n    private static StringContent CreateJsonContent<T>(T value, JsonTypeInfo<T> typeInfo)\n    {\n        return new StringContent(\n            JsonSerializer.Serialize(value, typeInfo),\n            System.Text.Encoding.UTF8,\n            \"application/json\"\n        );\n    }\n\n    private static string GetErrorMessage(string content)","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/Devolutions/UniGetUI/blob/9b1d7d0eab91620fc15c86b36676532095936ae3/src/UniGetUI.Interface.IpcApi/GitHubApiClient.cs#L215-L251","documentation":"GitHubApiClient.SendAsync is the single chokepoint for all GitHub REST/OAuth requests. After sending the HttpRequestMessage it reads the response body and checks IsSuccessStatusCode. On any non-2xx status it throws an HttpRequestException whose message includes the numeric status code, the ReasonPhrase, and a best-effort extraction of the error body (GetErrorMessage parses the JSON for message/error/error_description, falling back to the raw body). This surfaces rate limits, auth failures, 404s, and 5xx errors uniformly.","triggerScenarios":"Any GitHubApiClient call (GetCurrentUserAsync, GetCurrentUserGistsAsync, GetGistAsync, CreateGistAsync, EditGistAsync, device-flow token exchange) receives a non-success HTTP status. Examples: 401 when the stored token expired or was revoked; 403 with a rate-limit body; 404 when a gist ID no longer exists; 422 when a gist request payload is invalid; 5xx for GitHub outages.","commonSituations":"The GitHub OAuth token expired or was revoked (401). The client hit the unauthenticated rate limit of 60 req/hour (403). A gist was manually deleted between list and edit (404). The build's UNIGETUI_GITHUB_CLIENT_ID is wrong, producing a 404 or 401 on device-flow initiation. GitHub itself is down or degraded (5xx). Network proxy intercepts and returns a non-GitHub error body.","solutions":["Read the status code and body from the exception message to identify the specific GitHub error.","If 401/403: re-authenticate via the device flow (CompleteGitHubDeviceFlowAsync) and retry.","If 403 rate-limited: back off and retry after the Reset header interval; reduce request frequency.","If 404 on a gist: the backup gist was deleted — call CreateCloudBackupAsync to recreate it.","If 5xx: retry with exponential backoff or report a transient GitHub outage."],"exampleFix":"// before: unguarded GitHub call\nvar user = await client.GetCurrentUserAsync();\n// after: handle auth/rate-limit failures\ntry { var user = await client.GetCurrentUserAsync(); }\ncatch (HttpRequestException ex)\n{\n    if (ex.Message.Contains(\"401\") || ex.Message.Contains(\"403\"))\n        await IpcBackupApi.CompleteGitHubDeviceFlowAsync();\n    throw;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { var user = await client.GetCurrentUserAsync(cancellationToken); }\ncatch (HttpRequestException ex)\n{\n    Logger.Error($\"GitHub API failure: {ex.Message}\");\n    if (ex.Message.Contains(\"401\") || ex.Message.Contains(\"403\")) { /* re-authenticate */ }\n    if (ex.Message.Contains(\"403\") && ex.Message.Contains(\"rate limit\", StringComparison.OrdinalIgnoreCase)) { /* back off */ }\n}","preventionTips":["Cache GitHub responses to reduce request volume and avoid rate limits.","Use an authenticated client (higher rate limit) rather than anonymous where possible.","Implement exponential backoff for transient 5xx responses."],"tags":["github","http","api","network","ipc"],"backgroundTag":null,"analyzedSha":"9b1d7d0eab91620fc15c86b36676532095936ae3","analyzedAt":"2026-08-13T12:19:18.278Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}