{"record":{"id":"81bfd5b3003a6572","repo":"router-for-me/CLIProxyAPI","slug":"invalid-plugin-id-q-81bfd5","errorCode":null,"errorMessage":"invalid plugin id %q","messagePattern":"invalid plugin id %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/install.go","lineNumber":150,"sourceCode":"\t}\n\tif errVerify := VerifyChecksum(archiveAsset.Name, archiveData, checksums); errVerify != nil {\n\t\treturn InstallResult{}, errVerify\n\t}\n\tplugin.Version = version\n\tresult, errInstall := InstallArchive(archiveData, plugin, options)\n\tif errInstall != nil {\n\t\treturn InstallResult{}, errInstall\n\t}\n\tresult.InstallType = InstallTypeGitHubRelease\n\tresult.ReleaseTag = strings.TrimSpace(release.TagName)\n\treturn result, nil\n}\n\nfunc (c Client) InstallDirect(ctx context.Context, plugin Plugin, plan InstallPlan, options InstallOptions) (InstallResult, error) {\n\tplugin.ID = strings.TrimSpace(plugin.ID)\n\tplugin.Version = normalizeVersion(plugin.Version)\n\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}","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/install.go#L132-L168","documentation":"InstallDirect trimmed plugin.ID and rejected it via validPluginID. Plugin IDs must match the accepted identifier grammar (non-empty, restricted charset) because the ID becomes the archive name, install directory, and lookup key. The quoted value shows exactly what was rejected.","triggerScenarios":"Calling InstallDirect with an empty ID, whitespace-only ID, or one containing invalid characters (spaces, slashes, uppercase, or symbols outside the allowed set).","commonSituations":"Manifest authored with a display name in the id field ('My Plugin'); ID derived from user input without sanitization; missing id field defaulting to empty string.","solutions":["Set plugin.ID to a clean identifier (lowercase letters, digits, hyphens — e.g. 'my-plugin')","Sanitize user-supplied IDs before passing them to InstallDirect","Fix the id field in the source manifest and re-sync"],"exampleFix":"// before\nplugin.ID = \"My Plugin\"\n\n// after\nplugin.ID = \"my-plugin\"","handlingStrategy":"validation","validationCode":"id := strings.TrimSpace(plugin.ID)\nif !validID(id) { // e.g. regexp `^[a-z0-9][a-z0-9-]*$`\n    return fmt.Errorf(\"invalid plugin id %q\", id)\n}","typeGuard":"func validPluginIDString(id string) bool {\n    matched, _ := regexp.MatchString(`^[a-z0-9][a-z0-9-_]*$`, strings.TrimSpace(id))\n    return matched && id != \"\"\n}","tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"invalid plugin id\") {\n    // sanitize: lowercase, trim, replace spaces with hyphens; re-run install\n}","preventionTips":["Normalize user-supplied plugin IDs (lowercase, hyphens) before InstallDirect","Enforce the ID grammar in manifest authoring lint","Keep the same ID across versions — IDs are the install directory key"],"tags":["plugin-id","validation","install","go"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}