{"record":{"id":"caa9cc1aecf80f3a","repo":"siyuan-note/siyuan","slug":"get-github-releases-failed-d","errorCode":null,"errorMessage":"get GitHub releases failed: %d","messagePattern":"get GitHub releases failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/updater_release.go","lineNumber":247,"sourceCode":"\t\treturn nil, err\n\t}\n\treturn value.([]*githubRelease), nil\n}\n\nfunc fetchGitHubReleases(ctx context.Context) ([]*githubRelease, error) {\n\treleases := []*githubRelease{}\n\trequest := httpclient.NewCloudRequest30s().\n\t\tSetContext(ctx).\n\t\tSetHeader(\"Accept\", \"application/vnd.github+json\").\n\t\tSetHeader(\"X-GitHub-Api-Version\", \"2022-11-28\").\n\t\tSetSuccessResult(&releases)\n\tresponse, err := request.Get(githubReleasesURL)\n\tif err != nil {\n\t\tlogging.LogErrorf(\"get GitHub releases failed: %s\", err)\n\t\treturn nil, err\n\t}\n\tif 200 != response.StatusCode {\n\t\terr = fmt.Errorf(\"get GitHub releases failed: %d\", response.StatusCode)\n\t\tlogging.LogError(err.Error())\n\t\treturn nil, err\n\t}\n\tif 0 == len(releases) {\n\t\treturn nil, errors.New(\"GitHub releases are empty\")\n\t}\n\n\tgithubReleasesLock.Lock()\n\tcachedGitHubReleases = releases\n\tgithubReleasesCacheTime = time.Now().Unix()\n\tgithubReleasesLock.Unlock()\n\treturn releases, nil\n}\n\nfunc selectGitHubRelease(releases []*githubRelease, channel string) *githubRelease {\n\tvar selected *githubRelease\n\tfor _, release := range releases {\n\t\tif nil == release || release.Draft || !isReleaseAllowed(channel, release.TagName) {","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/updater_release.go#L229-L265","documentation":"Returned by fetchGitHubReleases(ctx) when the GitHub API responds with a status code other than 200. The numeric status is embedded in the message. The most common cause is 403 (rate limit) since the GitHub releases endpoint is unauthenticated. The error is logged via logging.LogError before returning.","triggerScenarios":"GitHub API returns 403 due to unauthenticated rate limiting (60 requests/hour per IP, shared across users behind a NAT/corporate proxy). 5xx during a GitHub outage. 304/other non-200 responses. A proxy returning an unexpected status.","commonSituations":"Many machines behind one public IP (office, CI, NAT) exhausting the 60/hour anonymous quota. GitHub incident. Corporate proxy intercepting api.github.com. Repeated force=true refreshes burning the quota.","solutions":["Wait for the rate-limit window to reset (check X-RateLimit-Reset header) and retry; the cache (6h, githubAPIReleaseCacheSeconds) reduces repeated calls.","Avoid force=true refreshes in loops — they bypass the cache and burn quota.","Switch to the stable channel, which uses the rhy cloud endpoint and does not hit the GitHub API.","If behind a shared NAT, consider routing GitHub API traffic through a distinct egress IP."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Retry with backoff; respect GitHub rate limits.\nvar releases []*githubRelease\nfor attempt := 0; attempt < 3; attempt++ {\n    rel, err := getGitHubReleases(ctx, attempt == 0)\n    if err == nil {\n        releases = rel\n        break\n    }\n    if !strings.Contains(err.Error(), \"get GitHub releases failed\") {\n        break // different error; don't retry blindly\n    }\n    time.Sleep(time.Duration(1<<attempt) * time.Minute)\n}","preventionTips":["Never call getGitHubReleases with force=true in a loop — it bypasses the 6h cache.","Prefer the stable channel (rhy endpoint) when many clients share one IP.","Surface 'rate limited, try later' to the user instead of a raw error."],"tags":["update","github-api","rate-limit","network","http-status"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}