router-for-me/CLIProxyAPI · error
invalid plugin id %q
Error message
invalid plugin id %q
What it means
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.
Source
Thrown at internal/pluginstore/install.go:150
}
if errVerify := VerifyChecksum(archiveAsset.Name, archiveData, checksums); errVerify != nil {
return InstallResult{}, errVerify
}
plugin.Version = version
result, errInstall := InstallArchive(archiveData, plugin, options)
if errInstall != nil {
return InstallResult{}, errInstall
}
result.InstallType = InstallTypeGitHubRelease
result.ReleaseTag = strings.TrimSpace(release.TagName)
return result, nil
}
func (c Client) InstallDirect(ctx context.Context, plugin Plugin, plan InstallPlan, options InstallOptions) (InstallResult, error) {
plugin.ID = strings.TrimSpace(plugin.ID)
plugin.Version = normalizeVersion(plugin.Version)
if !validPluginID(plugin.ID) {
return InstallResult{}, fmt.Errorf("invalid plugin id %q", plugin.ID)
}
if !validPluginVersion(plugin.Version) {
return InstallResult{}, fmt.Errorf("invalid plugin version %q", plugin.Version)
}
plan = NormalizeInstallPlan(plan)
plan.Type = InstallTypeDirect
if errValidate := ValidateInstallPlan(plan); errValidate != nil {
return InstallResult{}, errValidate
}
options = normalizeInstallOptions(options)
artifact, errSelect := SelectArtifact(plan, options.GOOS, options.GOARCH)
if errSelect != nil {
return InstallResult{}, errSelect
}
archiveData, errDownload := c.DownloadArtifact(ctx, artifact)
if errDownload != nil {
return InstallResult{}, fmt.Errorf("download artifact: %w", errDownload)
}View on GitHub (pinned to 78f0c4079e)
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
Example fix
// before plugin.ID = "My Plugin" // after plugin.ID = "my-plugin"
Defensive patterns
Strategy: validation
Validate before calling
id := strings.TrimSpace(plugin.ID)
if !validID(id) { // e.g. regexp `^[a-z0-9][a-z0-9-]*$`
return fmt.Errorf("invalid plugin id %q", id)
} Type guard
func validPluginIDString(id string) bool {
matched, _ := regexp.MatchString(`^[a-z0-9][a-z0-9-_]*$`, strings.TrimSpace(id))
return matched && id != ""
} Try / catch
if err != nil && strings.Contains(err.Error(), "invalid plugin id") {
// sanitize: lowercase, trim, replace spaces with hyphens; re-run install
} Prevention
- 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
When it happens
Trigger: Calling InstallDirect with an empty ID, whitespace-only ID, or one containing invalid characters (spaces, slashes, uppercase, or symbols outside the allowed set).
Common situations: 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.
Related errors
- invalid plugin version %q
- weight must not exceed %d
- weight must be an integer: %w
- weight must be an integer
- invalid auth file name
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/81bfd5b3003a6572.
Report an issue: GitHub.