router-for-me/CLIProxyAPI · error

asset %q missing download url

Error message

asset %q missing download url

What it means

Client.DownloadAsset picks a download URL for a GitHub release asset: it prefers asset.BrowserDownloadURL, but switches to asset.APIURL when the browser URL is empty or when releaseAssetAPIAuthenticated determines the API URL has configured auth (private repos need the API endpoint with token headers). 'asset %q missing download url' is thrown when both candidate URLs are empty/whitespace after that resolution.

Source

Thrown at internal/pluginstore/github.go:129

// leading "v"/"V" and validating the result.
func ReleaseVersion(release Release) (string, error) {
	version := normalizeVersion(release.TagName)
	if !validPluginVersion(version) {
		return "", fmt.Errorf("invalid release tag %q", release.TagName)
	}
	return version, nil
}

func (c Client) DownloadAsset(ctx context.Context, asset ReleaseAsset) ([]byte, error) {
	downloadURL := strings.TrimSpace(asset.BrowserDownloadURL)
	apiURL := strings.TrimSpace(asset.APIURL)
	if downloadURL == "" || c.releaseAssetAPIAuthenticated(apiURL) {
		if apiURL != "" {
			downloadURL = apiURL
		}
	}
	if downloadURL == "" {
		return nil, fmt.Errorf("asset %q missing download url", asset.Name)
	}
	return c.get(ctx, downloadURL, "application/octet-stream", RequestKindArtifact, 0)
}

func (c Client) releaseAssetAPIAuthenticated(apiURL string) bool {
	apiURL = strings.TrimSpace(apiURL)
	if apiURL == "" {
		return false
	}
	if item, ok := matchingResolvedAuthConfig(c.ResolvedAuth, apiURL, RequestKindArtifact); ok {
		return resolvedAuthConfigured(item)
	}
	return AuthConfigured(c.Auth, apiURL, RequestKindArtifact)
}

func (c Client) get(ctx context.Context, requestURL string, accept string, kind string, maxSize int64) ([]byte, error) {
	currentURL := strings.TrimSpace(requestURL)
	for redirects := 0; ; redirects++ {

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Log asset.Name and the raw API response to see which fields are absent on the failing asset.
  2. If you construct ReleaseAsset yourself (tests/tools), populate BrowserDownloadURL (from the release payload) before calling DownloadAsset.
  3. If GitHub responses genuinely lack the fields, re-fetch the release to get fresh asset objects.

Example fix

// before
for _, asset := range release.Assets {
    data, err := client.DownloadAsset(ctx, asset) // fixture without URLs -> error
}

// after
for _, asset := range release.Assets {
    if strings.TrimSpace(asset.BrowserDownloadURL) == "" && strings.TrimSpace(asset.APIURL) == "" {
        continue // skip assets with no downloadable URL
    }
    data, err := client.DownloadAsset(ctx, asset)
}
Defensive patterns

Strategy: validation

Validate before calling

for _, asset := range release.Assets {
    if strings.TrimSpace(asset.BrowserDownloadURL) == "" && strings.TrimSpace(asset.APIURL) == "" {
        continue // skip non-downloadable entries instead of erroring mid-loop
    }
}

Type guard

func assetDownloadable(a pluginstore.ReleaseAsset) bool {
    return strings.TrimSpace(a.BrowserDownloadURL) != "" || strings.TrimSpace(a.APIURL) != ""
}

Prevention

When it happens

Trigger: A ReleaseAsset JSON object from GitHub lacking both browser_download_url and url fields — either a hand-constructed Release, a filtered/rewritten API response, or an asset object of an unexpected kind (e.g. a data object where a source/browser URL was expected).

Common situations: Unit tests or fixtures building ReleaseAsset literals without URLs; API responses parsed into the wrong struct; processing a Release where the assets array was repackaged by a proxy or mirror that drops URL fields.

Related errors


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