{"record":{"id":"35660bec491b1b81","repo":"chenhg5/cc-connect","slug":"check-updates-failed-both-sources-w","errorCode":null,"errorMessage":"check updates failed (both sources): %w","messagePattern":"check updates failed \\(both sources\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/updater.go","lineNumber":94,"sourceCode":"\t\turl  string\n\t}\n\tsources := []source{\n\t\t{\"github\", githubReleasesAPI + \"?per_page=20\"},\n\t\t{\"gitee\", giteeReleasesAPI + \"?per_page=20&direction=desc&sort=created\"},\n\t}\n\tif preferGitee {\n\t\tsources[0], sources[1] = sources[1], sources[0]\n\t}\n\n\treleases, err := fetchReleasesFrom(sources[0].url)\n\tif err == nil && len(releases) > 0 {\n\t\treturn releases, nil\n\t}\n\tslog.Debug(\"updater: primary source failed, trying fallback\", \"primary\", sources[0].name, \"error\", err)\n\n\treleases, err = fetchReleasesFrom(sources[1].url)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"check updates failed (both sources): %w\", err)\n\t}\n\treturn releases, nil\n}\n\nfunc fetchReleasesFrom(apiURL string) ([]ReleaseInfo, error) {\n\tclient := &http.Client{Timeout: 15 * time.Second}\n\treq, err := http.NewRequest(\"GET\", apiURL, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"User-Agent\", \"cc-connect-updater\")\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/updater.go#L76-L112","documentation":"This error is returned by fetchReleases (core/updater.go:94) when BOTH release-list sources (GitHub API first, then Gitee as fallback) fail to return a valid release list. The wrapped error (`%w`) is the failure from the second (fallback) source; the primary source's error is only logged at debug level. Callers see it from CheckForUpdate, meaning no update check could be performed at all.","triggerScenarios":"Both fetchReleasesFrom calls fail: e.g. no network access, api.github.com blocked (common in mainland China without preferGitee), Gitee API rate-limited or returning non-200 (caught as error 916), 15s HTTP client timeout on both hosts, or both APIs returning malformed JSON that fails to decode into []ReleaseInfo.","commonSituations":"Developers running cc-connect behind a corporate proxy or firewall that blocks api.github.com while the Gitee fallback is also unreachable; GitHub API rate-limit (HTTP 403) when many checks originate from one IP; DNS failures on an offline host; transient network outage during `cc-connect upgrade` or startup update check.","solutions":["Check basic connectivity: `curl -sS https://api.github.com/repos/chenhg5/cc-connect/releases?per_page=1` and the Gitee equivalent to see which source and status code fails.","Set preferGitee=true in config (or use the flag) so the Gitee mirror is tried first if GitHub is blocked in your network.","Inspect the wrapped error text after 'both sources:' — it names the fallback failure (e.g. 'API returned 403', 'context deadline exceeded') and fix that root cause.","If behind a proxy, set HTTPS_PROXY so both hosts are reachable, or temporarily disable automatic update checks.","Retry later if it is a transient outage or GitHub rate limit (403 with Retry-After); rate limits reset hourly."],"exampleFix":"// before: check fails silently on primary, error mentions only fallback\nreleases, err = fetchReleasesFrom(sources[1].url)\nif err != nil {\n\treturn nil, fmt.Errorf(\"check updates failed (both sources): %w\", err)\n}\n\n// after: log both errors so the root cause of each source is visible\nif err != nil {\n\treturn nil, fmt.Errorf(\"check updates failed (primary: %v; fallback: %w)\", primaryErr, err)\n}","handlingStrategy":"fallback","validationCode":"func canCheckUpdates() error {\n\tfor _, u := range []string{\"https://api.github.com\", \"https://gitee.com/api/v5\"} {\n\t\tc := &http.Client{Timeout: 5 * time.Second}\n\t\tresp, err := c.Get(u)\n\t\tif err == nil {\n\t\t\tresp.Body.Close()\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn errors.New(\"neither github nor gitee API reachable\")\n}","typeGuard":null,"tryCatchPattern":"if best, err := core.CheckForUpdate(version, preferGitee); err != nil {\n\tvar netErr net.Error\n\tif errors.As(err, &netErr) || strings.Contains(err.Error(), \"both sources\") {\n\t\tslog.Warn(\"update check skipped; network to release APIs unavailable\", \"err\", err)\n\t} else {\n\t\tslog.Error(\"update check failed\", \"err\", err)\n\t}\n\t// proceed without update — non-fatal\n}","preventionTips":["Set preferGitee=true on networks where GitHub is blocked so the reachable mirror is tried first.","Configure HTTPS_PROXY correctly before running upgrade/check commands.","Skip update checks on known-offline hosts (air-gapped installs).","Don't hammer the check — rate limits on api.github.com can take both sources out via 403s.","Check the wrapped fallback error text first; it names the last failure cause."],"tags":["network","http","updater","github-api","gitee"],"backgroundTag":"api-request-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}