{"record":{"id":"7fb2d64a86514f3d","repo":"router-for-me/CLIProxyAPI","slug":"direct-install-manifest-missing-source-url","errorCode":null,"errorMessage":"direct install manifest missing source-url","messagePattern":"direct install manifest missing source-url","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/install.go","lineNumber":193,"sourceCode":"\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 {\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) {","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/install.go#L175-L211","documentation":"Thrown when resolving a direct-install plugin from a manifest that declares no inline artifacts (len(plugin.Install.Artifacts) == 0). In that case the client must fall back to a registry to resolve artifacts, using manifest.SourceURL first and c.RegistryURL second. If both are empty after trimming, there is nowhere to resolve the plugin from, so the operation fails. The library requires at least one resolution source to guarantee the artifact origin is known and auditable.","triggerScenarios":"Calling the manifest-based direct install path with a manifest that has no Install.Artifacts entries, while both manifest.SourceURL is blank/whitespace and the Client was constructed with an empty RegistryURL (zero-value Client or RegistryURL never set).","commonSituations":"Constructing pluginstore.Client{} via struct literal and forgetting to set RegistryURL; a hand-written manifest.json that omits the source-url field; trimming/removing the registry URL during config refactoring; tests using a zero-value client.","solutions":["Set manifest.SourceURL to the registry base URL that serves the plugin","Or set c.RegistryURL on the Client before calling the install API","Or embed the artifact list (Install.Artifacts with URLs/checksums) directly in the manifest so no registry resolution is needed"],"exampleFix":"// before\nclient := pluginstore.Client{} // RegistryURL empty\nplugin, err := client.ResolveDirect(ctx, manifest) // manifest.SourceURL == \"\"\n\n// after\nclient := pluginstore.Client{RegistryURL: \"https://plugins.example.com/registry.json\"}\nplugin, err := client.ResolveDirect(ctx, manifest)","handlingStrategy":"validation","validationCode":"func manifestResolvable(c pluginstore.Client, manifest Manifest) error {\n    if len(manifest.Plugin().Install.Artifacts) > 0 {\n        return nil // inline artifacts, no registry needed\n    }\n    if strings.TrimSpace(manifest.SourceURL) == \"\" && strings.TrimSpace(c.RegistryURL) == \"\" {\n        return errors.New(\"manifest has no artifacts and no source-url; set manifest.SourceURL or Client.RegistryURL\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := client.ResolveDirect(ctx, manifest); err != nil {\n    if strings.Contains(err.Error(), \"missing source-url\") {\n        return errors.New(\"configuration incomplete: provide a registry URL\")\n    }\n}","preventionTips":["Always construct the Client through a factory that requires a non-empty RegistryURL","Lint manifests at build time: either artifacts or source-url must be present","Add a unit test asserting a zero-value Client fails fast with this exact message"],"tags":["go","plugin-store","manifest","configuration"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}