{"record":{"id":"4f4ffb4742a1683b","repo":"router-for-me/CLIProxyAPI","slug":"download-artifact-w","errorCode":null,"errorMessage":"download artifact: %w","messagePattern":"download artifact: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/install.go","lineNumber":167,"sourceCode":"\tif !validPluginID(plugin.ID) {\n\t\treturn InstallResult{}, fmt.Errorf(\"invalid plugin id %q\", plugin.ID)\n\t}\n\tif !validPluginVersion(plugin.Version) {\n\t\treturn InstallResult{}, fmt.Errorf(\"invalid plugin version %q\", plugin.Version)\n\t}\n\tplan = NormalizeInstallPlan(plan)\n\tplan.Type = InstallTypeDirect\n\tif errValidate := ValidateInstallPlan(plan); errValidate != nil {\n\t\treturn InstallResult{}, errValidate\n\t}\n\toptions = normalizeInstallOptions(options)\n\tartifact, errSelect := SelectArtifact(plan, options.GOOS, options.GOARCH)\n\tif errSelect != nil {\n\t\treturn InstallResult{}, errSelect\n\t}\n\tarchiveData, errDownload := c.DownloadArtifact(ctx, artifact)\n\tif errDownload != nil {\n\t\treturn InstallResult{}, fmt.Errorf(\"download artifact: %w\", errDownload)\n\t}\n\tif errVerify := VerifyArtifactChecksum(artifact, archiveData); errVerify != nil {\n\t\treturn InstallResult{}, errVerify\n\t}\n\tresult, errInstall := InstallArchive(archiveData, plugin, options)\n\tif errInstall != nil {\n\t\treturn InstallResult{}, errInstall\n\t}\n\tresult.InstallType = InstallTypeDirect\n\treturn result, nil\n}\n\nfunc (c Client) directPluginFromManifest(ctx context.Context, manifest Manifest) (Plugin, error) {\n\tplugin := manifest.Plugin()\n\tplugin.Version = normalizeVersion(manifest.Version)\n\tplugin.Install = NormalizeInstallPlan(plugin.Install)\n\tplugin.Install.Type = InstallTypeDirect\n\tif len(plugin.Install.Artifacts) > 0 {","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/install.go#L149-L185","documentation":"Wrapped error returned by Client.InstallDirect when c.DownloadArtifact(ctx, artifact) fails. The artifact was successfully selected from the install plan (matching GOOS/GOARCH), but the HTTP fetch of the artifact bytes failed. The underlying error (network failure, DNS error, HTTP 4xx/5xx, TLS problem) is preserved via %w so callers can inspect it with errors.Is/errors.As.","triggerScenarios":"InstallDirect with an install plan whose artifact URLs point to a host that is unreachable, returns 404/403, has an expired signed URL, or when the machine has no network egress; also triggered by a proxy or firewall blocking the artifact host, or the server closing the connection mid-download.","commonSituations":"CI runners without outbound network access; artifact URLs from a stale registry snapshot whose CDN links expired; corporate proxies intercepting TLS; transient network flakiness during long downloads; wrong RegistryURL pointing at a host that serves HTML error pages.","solutions":["Inspect the wrapped error (errors.Unwrap / %w chain) to identify the real cause: HTTP status, DNS, or timeout","Verify the artifact URL from the install plan resolves in curl before retrying","Retry with backoff for transient network failures; re-fetch the registry first if signed URLs may have expired","Check proxy/firewall/egress rules for the artifact host"],"exampleFix":"result, err := client.InstallDirect(ctx, plugin, plan, options)\nif err != nil {\n    if strings.Contains(err.Error(), \"download artifact:\") {\n        var urlErr *url.Error\n        if errors.As(err, &urlErr) {\n            log.Errorf(\"network failure downloading artifact: %v\", urlErr)\n        }\n    }\n}\n\n// with retry for transient failures:\nvar result pluginstore.InstallResult\nerr := retry(3, time.Second, func() error {\n    var e error\n    result, e = client.InstallDirect(ctx, plugin, plan, options)\n    if e != nil && strings.Contains(e.Error(), \"download artifact:\") {\n        return e // retryable\n    }\n    if e != nil {\n        return retry.Stop(e) // not retryable\n    }\n    return nil\n})","handlingStrategy":"retry","validationCode":"func artifactReachable(ctx context.Context, artifactURL string) error {\n    req, _ := http.NewRequestWithContext(ctx, http.MethodHead, artifactURL, nil)\n    resp, err := http.DefaultClient.Do(req)\n    if err != nil {\n        return err\n    }\n    defer func() { _ = resp.Body.Close() }()\n    if resp.StatusCode >= 400 {\n        return fmt.Errorf(\"artifact URL returned %d\", resp.StatusCode)\n    }\n    return nil\n}","typeGuard":"func isDownloadArtifactError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"download artifact:\")\n}","tryCatchPattern":"var result pluginstore.InstallResult\nerr := installWithBackoff(ctx, client, plugin, plan, options, &result, 3)\nfunc installWithBackoff(...) error {\n    for attempt := 0; attempt < 3; attempt++ {\n        r, e := client.InstallDirect(ctx, plugin, plan, options)\n        if e == nil { *out = r; return nil }\n        if !isDownloadArtifactError(e) { return e } // only retry network step\n        var netErr net.Error\n        if !errors.As(e, &netErr) { return e } // non-network cause: stop\n        time.Sleep(time.Duration(attempt+1) * time.Second)\n    }\n    return errors.New(\"download artifact: exhausted retries\")\n}","preventionTips":["HEAD-check artifact URLs before starting an install batch","Re-fetch the registry before retrying when signed/expiring URLs are in play","Wrap installs in bounded retry with backoff for transient network errors only"],"tags":["go","plugin-store","network","download","install"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}