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
- Log asset.Name and the raw API response to see which fields are absent on the failing asset.
- If you construct ReleaseAsset yourself (tests/tools), populate BrowserDownloadURL (from the release payload) before calling DownloadAsset.
- 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
- Filter assets by platform and URL presence before downloading.
- When building Release fixtures in tests, always set BrowserDownloadURL.
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
- plugin store auth missing header-name
- unsupported plugin store auth type %q
- plugin store resolved auth missing header-name
- invalid plugin store url
- install type %q is not direct
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/af460e97ddff4237.
Report an issue: GitHub.