{"record":{"id":"685a2eecb5bf71cb","repo":"router-for-me/CLIProxyAPI","slug":"invalid-plugin-id-q-685a2e","errorCode":null,"errorMessage":"invalid plugin id %q","messagePattern":"invalid plugin id %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginstore/manifest.go","lineNumber":172,"sourceCode":"\t\t\treturn fmt.Errorf(\"artifacts[%d]: invalid artifact url\", index)\n\t\t}\n\t\tif parsed.User != nil {\n\t\t\treturn fmt.Errorf(\"artifacts[%d]: pinned artifact url must not contain credentials\", index)\n\t\t}\n\t\tif parsed.RawQuery != \"\" || parsed.Fragment != \"\" {\n\t\t\treturn fmt.Errorf(\"artifacts[%d]: pinned artifact url must not contain query or fragment\", index)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validateManifestPluginID(id string) error {\n\tid = strings.TrimSpace(id)\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"missing required field id\")\n\t}\n\tif !validPluginID(id) {\n\t\treturn fmt.Errorf(\"invalid plugin id %q\", id)\n\t}\n\treturn nil\n}\n\nfunc validateManifestSourceURL(sourceURL string) error {\n\tsourceURL = strings.TrimSpace(sourceURL)\n\tif sourceURL == \"\" {\n\t\treturn fmt.Errorf(\"missing required field source-url\")\n\t}\n\tparsed, errParse := url.Parse(sourceURL)\n\tif errParse != nil || parsed.Scheme == \"\" || parsed.Host == \"\" {\n\t\treturn fmt.Errorf(\"invalid source-url\")\n\t}\n\tif parsed.Scheme != \"https\" && parsed.Scheme != \"http\" {\n\t\treturn fmt.Errorf(\"source-url must use http or https\")\n\t}\n\tif hasSensitiveQueryParameter(parsed) {\n\t\treturn fmt.Errorf(\"source-url contains sensitive query parameter\")","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/manifest.go#L154-L190","documentation":"validateManifestPluginID() requires the id (after trim) to match ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$: start with an ASCII letter or digit, continue with letters/digits/dot/underscore/hyphen only, max 128 chars. Spaces, slashes, colons, unicode, or a leading punctuation char all fail.","triggerScenarios":"Direct-install manifest with id like \"acme/plugin\" (slash), \"my plugin\" (space), \"1.0:plug\" (colon), a 129+ char id, or one starting with \"_\" or \".\".","commonSituations":"Using a repository path (owner/repo) as the plugin id; ids copied from display names with spaces; i18n ids with non-ASCII characters; very long generated ids exceeding 128 chars.","solutions":["Rewrite the id to the allowed alphabet: alphanumerics, dots, underscores, hyphens; e.g. \"acme.repo-plugin\"","Ensure the first character is a letter or digit (not '.', '_', '-')","Keep it under 128 characters and avoid spaces/slashes entirely"],"exampleFix":"# before\nid: \"acme/plugin one\"\n# invalid plugin id \"acme/plugin one\"\n\n# after\nid: \"acme.plugin-one\"","handlingStrategy":"validation","validationCode":"var idRe = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`)\nif !idRe.MatchString(strings.TrimSpace(m.ID)) { return fmt.Errorf(\"bad id %q\", m.ID) }\n// or reuse the public helper from pluginhost:\nif !pluginhost.ValidatePluginID(strings.TrimSpace(m.ID)) { return errors.New(\"bad id\") }","typeGuard":"func validPluginIDString(id string) bool { return regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$`).MatchString(strings.TrimSpace(id)) }","tryCatchPattern":"if err := m.Validate(); err != nil && strings.Contains(err.Error(), \"invalid plugin id\") { /* slugify: replace / and space with '-', check length<=128, re-validate */ }","preventionTips":["Slugify ids when generating from repo names (owner/repo → owner.repo)","Cap ids at 128 chars at generation time","Reuse pluginhost.ValidatePluginID for one source of truth"],"tags":["pluginstore","manifest","id","regex-validation"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}