{"record":{"id":"16131a604e5f200e","repo":"router-for-me/CLIProxyAPI","slug":"fetch-direct-install-source-w","errorCode":null,"errorMessage":"fetch direct install source: %w","messagePattern":"fetch direct install source: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/install.go","lineNumber":199,"sourceCode":"\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 {\n\t\treturn plugin, nil\n\t}\n\tsourceURL := strings.TrimSpace(manifest.SourceURL)\n\tif sourceURL == \"\" {\n\t\tsourceURL = strings.TrimSpace(c.RegistryURL)\n\t}\n\tif sourceURL == \"\" {\n\t\treturn Plugin{}, fmt.Errorf(\"direct install manifest missing source-url\")\n\t}\n\tsourceClient := c\n\tsourceClient.RegistryURL = sourceURL\n\tregistry, errRegistry := sourceClient.FetchRegistry(ctx)\n\tif errRegistry != nil {\n\t\treturn Plugin{}, fmt.Errorf(\"fetch direct install source: %w\", errRegistry)\n\t}\n\tresolved, okPlugin := registry.PluginByID(manifest.ID)\n\tif !okPlugin {\n\t\treturn Plugin{}, fmt.Errorf(\"direct install plugin %q not found in source\", strings.TrimSpace(manifest.ID))\n\t}\n\tif PluginInstallType(resolved) != InstallTypeDirect {\n\t\treturn Plugin{}, fmt.Errorf(\"direct install plugin %q resolved as %q\", strings.TrimSpace(manifest.ID), PluginInstallType(resolved))\n\t}\n\treturn directPluginVersion(resolved, manifest.ID, manifest.Version)\n}\n\nfunc directPluginVersion(plugin Plugin, id string, version string) (Plugin, error) {\n\tid = strings.TrimSpace(id)\n\tversion = normalizeVersion(version)\n\tif normalizeVersion(plugin.Version) == version {\n\t\tplugin.Version = version\n\t\tplugin.Install = NormalizeInstallPlan(plugin.Install)\n\t\tplugin.Install.Type = InstallTypeDirect","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/install.go#L181-L217","documentation":"Wrapped error returned when sourceClient.FetchRegistry(ctx) fails during direct-install resolution. The client re-points its RegistryURL at the resolved source (manifest.SourceURL or the client's own RegistryURL) and fetches the plugin registry document from it; any HTTP, parsing, or network failure at that step surfaces here with the cause preserved via %w.","triggerScenarios":"Direct manifest install where the source URL is unreachable, returns a non-2xx status, serves malformed registry JSON, requires auth that was not provided, or when DNS/TLS fails for the registry host.","commonSituations":"SourceURL with a typo or wrong scheme; self-hosted registry temporarily down; registry behind auth (401/403) while the client sends no credentials; CDN serving an HTML error page instead of JSON; certificate mismatch for private registries.","solutions":["curl the registry URL (sourceURL + registry document path) to confirm it returns valid JSON with HTTP 200","Fix scheme/host typos in manifest.SourceURL or Client.RegistryURL","If the registry requires auth or custom headers, configure them on the Client's HTTP transport before installing","Retry with backoff for transient 5xx/network failures"],"exampleFix":"// before\nmanifest.SourceURL = \"https://plugins.example\" // wrong host, FetchRegistry fails\n\n// after\nmanifest.SourceURL = \"https://plugins.example.com/registry.json\"\nplugin, err := client.ResolveDirect(ctx, manifest)\nif err != nil {\n    var netErr net.Error\n    if errors.As(err, &netErr) && netErr.Timeout() {\n        // retry with backoff\n    }\n}","handlingStrategy":"retry","validationCode":"func registryFetchable(ctx context.Context, registryURL string) error {\n    resp, err := http.Get(registryURL) //nolint:noctx // preflight only\n    if err != nil {\n        return fmt.Errorf(\"registry unreachable: %w\", err)\n    }\n    defer func() { _ = resp.Body.Close() }()\n    if resp.StatusCode != http.StatusOK {\n        return fmt.Errorf(\"registry returned %d\", resp.StatusCode)\n    }\n    return nil\n}","typeGuard":"func isFetchRegistryError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"fetch direct install source:\")\n}","tryCatchPattern":"if err != nil {\n    if isFetchRegistryError(err) {\n        var netErr net.Error\n        if errors.As(err, &netErr) || errors.Is(err, context.DeadlineExceeded) {\n            // transient: retry with backoff\n        } else {\n            // permanent: log registryURL and alert\n        }\n    }\n}","preventionTips":["Preflight the registry URL with a GET and JSON-decode check","Cache the last good registry document as a fallback source","Monitor registry reachability from the environment that runs installs"],"tags":["go","plugin-store","network","registry","manifest"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}