{"record":{"id":"8a913b9303600bb6","repo":"astrid-runtime/astrid","slug":"release-has-no-asset-list","errorCode":null,"errorMessage":"release has no asset list","messagePattern":"release has no asset list","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/self_update/mod.rs","lineNumber":308,"sourceCode":"    }\n    if method.manages_own_binary() {\n        return UpdatePlan::DeferToManager {\n            manager: method.label(),\n            how: method.upgrade_command(channel),\n        };\n    }\n    UpdatePlan::ApplyInPlace\n}\n\n/// Find exactly one release asset and return its browser download URL.\npub(super) fn exact_asset_url<'a>(\n    release: &'a serde_json::Value,\n    name: &str,\n) -> anyhow::Result<&'a str> {\n    let assets = release\n        .get(\"assets\")\n        .and_then(serde_json::Value::as_array)\n        .ok_or_else(|| anyhow::anyhow!(\"release has no asset list\"))?;\n    anyhow::ensure!(\n        assets.len() <= MAX_RELEASE_ASSETS,\n        \"release contains too many assets\"\n    );\n    let mut matches = assets\n        .iter()\n        .filter(|asset| asset.get(\"name\").and_then(|value| value.as_str()) == Some(name));\n    let asset = matches\n        .next()\n        .ok_or_else(|| anyhow::anyhow!(\"release has no asset '{name}'\"))?;\n    anyhow::ensure!(\n        matches.next().is_none(),\n        \"release contains duplicate asset '{name}'\"\n    );\n    asset\n        .get(\"browser_download_url\")\n        .and_then(|value| value.as_str())\n        .filter(|url| !url.is_empty())","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/self_update/mod.rs#L290-L326","documentation":"exact_asset_url looks up a named asset in a GitHub release JSON object. If the release payload has no 'assets' array (missing field or non-array), it throws this error. It guards against malformed or unexpected release responses before any asset matching happens.","triggerScenarios":"fetch_release_by_tag or another release fetch returns JSON lacking an 'assets' array — e.g. a GitHub API error body, a draft release with no assets, or a proxy/mirror returning reshaped JSON — then any consumer (publisher_bundle_url, integrity_manifest_url, download_verify_extract, resolve_signed_channel, resolve_target_blake3) calls exact_asset_url.","commonSituations":"GitHub API rate-limit or error JSON being parsed as if it were a release; querying a draft/prerelease that has no assets uploaded yet; a corporate proxy that rewrites API responses.","solutions":["Verify the fetched release is a real GitHub release JSON containing an 'assets' array (curl the releases/tag/<tag> endpoint).","Check for GitHub API rate limiting (403 with rate-limit body) or auth problems and add a token if needed.","Ensure the release has had its build artifacts uploaded; re-run the release workflow if assets are missing.","Bypass any proxy/mirror that strips or reshapes the assets field."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// before trusting the release payload\nconst release = JSON.parse(body);\nif (!Array.isArray(release.assets)) throw new Error(\"response is not a release object\");","typeGuard":"fn is_release_json(v: &serde_json::Value) -> bool {\n    v.get(\"assets\").and_then(serde_json::Value::as_array).is_some()\n}","tryCatchPattern":"let assets = release.get(\"assets\")\n    .and_then(serde_json::Value::as_array)\n    .ok_or_else(|| anyhow::anyhow!(\"release has no asset list\"))\n    .with_context(|| format!(\"release payload: {}\", release))?;","preventionTips":["Verify GitHub API responses (check HTTP status before parsing).","Add a GitHub token to avoid rate-limit error bodies being parsed as releases.","Don't query draft/prerelease tags that may lack assets."],"tags":["github","release","self-update","unexpected-response"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}