{"record":{"id":"d85ba58adc6ef1ea","repo":"MHSanaei/3x-ui","slug":"github-api-error-s","errorCode":null,"errorMessage":"GitHub API error: %s","messagePattern":"GitHub API error: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/web/service/server.go","lineNumber":798,"sourceCode":"\n\treq, reqErr := http.NewRequestWithContext(context.Background(), http.MethodGet, XrayURL, nil)\n\tif reqErr != nil {\n\t\treturn nil, reqErr\n\t}\n\tresp, err := s.settingService.NewProxiedHTTPClient(10 * time.Second).Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// Check HTTP status code - GitHub API returns object instead of array on error\n\tif resp.StatusCode != http.StatusOK {\n\t\tbodyBytes, _ := io.ReadAll(resp.Body)\n\t\tvar errorResponse struct {\n\t\t\tMessage string `json:\"message\"`\n\t\t}\n\t\tif json.Unmarshal(bodyBytes, &errorResponse) == nil && errorResponse.Message != \"\" {\n\t\t\treturn nil, fmt.Errorf(\"GitHub API error: %s\", errorResponse.Message)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"GitHub API returned status %d: %s\", resp.StatusCode, resp.Status)\n\t}\n\n\tbuffer := bytes.NewBuffer(make([]byte, bufferSize))\n\tbuffer.Reset()\n\tif _, err := buffer.ReadFrom(resp.Body); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar releases []Release\n\tif err := json.Unmarshal(buffer.Bytes(), &releases); err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar versions []string\n\tfor _, release := range releases {\n\t\ttagVersion := strings.TrimPrefix(release.TagName, \"v\")","sourceCodeStart":780,"sourceCodeEnd":816,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/web/service/server.go#L780-L816","documentation":"Returned by the release-list fetch (GetXrayVersions path) when the GitHub API answers non-200 AND the JSON body contains a non-empty \"message\" field — GitHub's standard error envelope. The message is surfaced verbatim, e.g. 'API rate limit exceeded for 1.2.3.4' or 'Bad credentials'. It exists because the caller expects an array of releases but GitHub returns an error object, which would otherwise fail confusingly during json.Unmarshal.","triggerScenarios":"Unauthenticated quota exhausted (403 + rate-limit message); an invalid GITHUB_TOKEN (401 'Bad credentials'); requesting the XTLS/Xray-core releases list too often from a shared IP.","commonSituations":"Panels on cloud NAT IPs sharing the 60 req/h anonymous budget; update checks on a cron with short intervals; a revoked or typo'd token in the environment.","solutions":["Read the message: 'rate limit' → wait for the hourly window reset or supply GITHUB_TOKEN; 'Bad credentials' → fix or remove the token","Reduce the polling frequency of version checks","Retry after the Retry-After / X-RateLimit-Reset window indicated by the API"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"versions, err := svc.GetXrayVersions()\nif err != nil && strings.Contains(err.Error(), \"rate limit\") {\n    // wait for X-RateLimit-Reset window or set GITHUB_TOKEN, then retry\n}\n","preventionTips":["Use an authenticated token for GitHub API calls on shared IPs","Cache the release list with a TTL of minutes, not seconds","Distinguish message-bearing errors (this one) from bare status errors when choosing retry policy"],"tags":["github-api","rate-limit","xray-update"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}