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

  1. Set plugin.ID to a clean identifier (lowercase letters, digits, hyphens — e.g. 'my-plugin')
  2. Sanitize user-supplied IDs before passing them to InstallDirect
  3. 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

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


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/81bfd5b3003a6572. Report an issue: GitHub.