router-for-me/CLIProxyAPI · error

fetch direct install source: %w

Error message

fetch direct install source: %w

What it means

Wrapped error returned when sourceClient.FetchRegistry(ctx) fails during direct-install resolution. The client re-points its RegistryURL at the resolved source (manifest.SourceURL or the client's own RegistryURL) and fetches the plugin registry document from it; any HTTP, parsing, or network failure at that step surfaces here with the cause preserved via %w.

Source

Thrown at internal/pluginstore/install.go:199

	plugin := manifest.Plugin()
	plugin.Version = normalizeVersion(manifest.Version)
	plugin.Install = NormalizeInstallPlan(plugin.Install)
	plugin.Install.Type = InstallTypeDirect
	if len(plugin.Install.Artifacts) > 0 {
		return plugin, nil
	}
	sourceURL := strings.TrimSpace(manifest.SourceURL)
	if sourceURL == "" {
		sourceURL = strings.TrimSpace(c.RegistryURL)
	}
	if sourceURL == "" {
		return Plugin{}, fmt.Errorf("direct install manifest missing source-url")
	}
	sourceClient := c
	sourceClient.RegistryURL = sourceURL
	registry, errRegistry := sourceClient.FetchRegistry(ctx)
	if errRegistry != nil {
		return Plugin{}, fmt.Errorf("fetch direct install source: %w", errRegistry)
	}
	resolved, okPlugin := registry.PluginByID(manifest.ID)
	if !okPlugin {
		return Plugin{}, fmt.Errorf("direct install plugin %q not found in source", strings.TrimSpace(manifest.ID))
	}
	if PluginInstallType(resolved) != InstallTypeDirect {
		return Plugin{}, fmt.Errorf("direct install plugin %q resolved as %q", strings.TrimSpace(manifest.ID), PluginInstallType(resolved))
	}
	return directPluginVersion(resolved, manifest.ID, manifest.Version)
}

func directPluginVersion(plugin Plugin, id string, version string) (Plugin, error) {
	id = strings.TrimSpace(id)
	version = normalizeVersion(version)
	if normalizeVersion(plugin.Version) == version {
		plugin.Version = version
		plugin.Install = NormalizeInstallPlan(plugin.Install)
		plugin.Install.Type = InstallTypeDirect

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. curl the registry URL (sourceURL + registry document path) to confirm it returns valid JSON with HTTP 200
  2. Fix scheme/host typos in manifest.SourceURL or Client.RegistryURL
  3. If the registry requires auth or custom headers, configure them on the Client's HTTP transport before installing
  4. Retry with backoff for transient 5xx/network failures

Example fix

// before
manifest.SourceURL = "https://plugins.example" // wrong host, FetchRegistry fails

// after
manifest.SourceURL = "https://plugins.example.com/registry.json"
plugin, err := client.ResolveDirect(ctx, manifest)
if err != nil {
    var netErr net.Error
    if errors.As(err, &netErr) && netErr.Timeout() {
        // retry with backoff
    }
}
Defensive patterns

Strategy: retry

Validate before calling

func registryFetchable(ctx context.Context, registryURL string) error {
    resp, err := http.Get(registryURL) //nolint:noctx // preflight only
    if err != nil {
        return fmt.Errorf("registry unreachable: %w", err)
    }
    defer func() { _ = resp.Body.Close() }()
    if resp.StatusCode != http.StatusOK {
        return fmt.Errorf("registry returned %d", resp.StatusCode)
    }
    return nil
}

Type guard

func isFetchRegistryError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "fetch direct install source:")
}

Try / catch

if err != nil {
    if isFetchRegistryError(err) {
        var netErr net.Error
        if errors.As(err, &netErr) || errors.Is(err, context.DeadlineExceeded) {
            // transient: retry with backoff
        } else {
            // permanent: log registryURL and alert
        }
    }
}

Prevention

When it happens

Trigger: Direct manifest install where the source URL is unreachable, returns a non-2xx status, serves malformed registry JSON, requires auth that was not provided, or when DNS/TLS fails for the registry host.

Common situations: SourceURL with a typo or wrong scheme; self-hosted registry temporarily down; registry behind auth (401/403) while the client sends no credentials; CDN serving an HTML error page instead of JSON; certificate mismatch for private registries.

Related errors


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