{"record":{"id":"c3d3386a1740a093","repo":"router-for-me/CLIProxyAPI","slug":"direct-plugin-sync-manifest-requires-pinned-artifa","errorCode":null,"errorMessage":"direct plugin sync manifest requires pinned artifacts","messagePattern":"direct plugin sync manifest requires pinned artifacts","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/home_sync.go","lineNumber":89,"sourceCode":"\t\t\treturn fmt.Errorf(\"plugin sync response contains duplicate plugin %q\", id)\n\t\t}\n\t\tseen[id] = struct{}{}\n\t\tfor authIndex := range item.Auth {\n\t\t\tif errAuth := ValidateResolvedAuthConfig(item.Auth[authIndex]); errAuth != nil {\n\t\t\t\treturn fmt.Errorf(\"plugin sync item %d auth %d: %w\", index, authIndex, errAuth)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validatePluginSyncManifestURLs(manifest Manifest) error {\n\tif manifest.InstallType() != InstallTypeDirect {\n\t\treturn nil\n\t}\n\tplan := NormalizeInstallPlan(manifest.Install)\n\tif len(plan.Artifacts) == 0 {\n\t\treturn fmt.Errorf(\"direct plugin sync manifest requires pinned artifacts\")\n\t}\n\tfor index, artifact := range plan.Artifacts {\n\t\tparsed, errParse := url.Parse(strings.TrimSpace(artifact.URL))\n\t\tif errParse != nil || !strings.EqualFold(parsed.Scheme, \"https\") {\n\t\t\treturn fmt.Errorf(\"direct plugin sync artifact %d must use https\", index)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (r *PluginSyncResponse) Clear() {\n\tif r == nil {\n\t\treturn\n\t}\n\tfor index := range r.Items {\n\t\tr.Items[index].Clear()\n\t}\n\tr.Items = nil","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/home_sync.go#L71-L107","documentation":"validatePluginSyncManifestURLs found an install_type=direct manifest whose normalized install plan contains zero artifacts. Direct installs download pinned artifacts, so an empty plan is meaningless and rejected. Non-direct install types bypass this check.","triggerScenarios":"Validate on a direct-install manifest whose install block is empty, has an empty artifacts array, or whose artifacts normalize away (nil entries).","commonSituations":"Index generator emitting install metadata only for github-release plugins but labeling everything 'direct'; manifest hand-edit that dropped the artifacts array.","solutions":["Add at least one pinned artifact (url + digest) to the direct manifest's install plan","If the plugin installs from GitHub releases, set install type to github-release instead","Fix the index generator so direct entries always carry artifacts"],"exampleFix":"// before\n{\"install\":{\"type\":\"direct\"}}\n\n// after\n{\"install\":{\"type\":\"direct\",\"artifacts\":[{\"url\":\"https://example.com/p_1.0.0_linux_amd64.zip\",\"sha256\":\"...\"}]}}","handlingStrategy":"validation","validationCode":"if m.InstallType() == pluginstore.InstallTypeDirect {\n    if len(pluginstore.NormalizeInstallPlan(m.Install).Artifacts) == 0 {\n        return errors.New(\"direct manifest has no pinned artifacts\")\n    }\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"requires pinned artifacts\") {\n    // switch entry to github-release type or add pinned artifacts; never bypass\n}","preventionTips":["Index lint: direct entries must have a non-empty artifacts array","Default entries to the github-release install type unless artifacts are truly pinned","Test published manifests through the client's Validate before rollout"],"tags":["manifest","install-plan","sync","validation","go"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}