{"record":{"id":"0f9dc43854f6c4c4","repo":"zeroclaw-labs/zeroclaw","slug":"plugin-archive-returned-http-status-for-url","errorCode":null,"errorMessage":"plugin archive returned HTTP {status} for {url}","messagePattern":"plugin archive returned HTTP (.+?) for (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/plugin_registry.rs","lineNumber":111,"sourceCode":"    extract_zip_safe(std::io::Cursor::new(bytes), &extract_dir)?;\n    let plugin_dir = find_manifest_dir(&extract_dir)?;\n    let manifest = load_plugin_manifest(&plugin_dir)?;\n    verify_manifest_matches_registry(&entry, &manifest)?;\n\n    Ok(DownloadedPlugin {\n        _temp_dir: temp_dir,\n        plugin_dir,\n        manifest,\n    })\n}\n\nasync fn download_archive_bytes(url: &str) -> Result<Vec<u8>> {\n    let mut response = reqwest::get(url)\n        .await\n        .with_context(|| format!(\"downloading plugin archive {url}\"))?;\n    let status = response.status();\n    if !status.is_success() {\n        bail!(\"plugin archive returned HTTP {status} for {url}\");\n    }\n    if let Some(len) = response.content_length()\n        && len > MAX_PLUGIN_ZIP_BYTES as u64\n    {\n        bail!(\"plugin archive exceeds maximum size of {MAX_PLUGIN_ZIP_BYTES} bytes\");\n    }\n\n    let mut bytes = Vec::new();\n    while let Some(chunk) = response\n        .chunk()\n        .await\n        .context(\"reading plugin archive response body\")?\n    {\n        append_chunk_capped(&mut bytes, &chunk, MAX_PLUGIN_ZIP_BYTES)?;\n    }\n    Ok(bytes)\n}\n","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/src/plugin_registry.rs#L93-L129","documentation":"A plugin archive download (the zip artifact referenced by a registry entry) returned a non-success HTTP status. download_archive_bytes GETs the archive URL from the registry entry and bails with the exact status and URL on any non-2xx response.","triggerScenarios":"Calling plugin install where the registry index loaded fine but the archive URL inside it is broken: 404 (artifact not uploaded / moved), 403 (expired pre-signed link), 429, or 5xx on the artifact host.","commonSituations":"Registry index is newer/older than the storage: entry points at an artifact that was deleted or not yet uploaded; CDN/auth link expired; artifact host outage; mirror serving metadata but not binaries.","solutions":["Confirm the artifact URL from the error is actually reachable: curl -I <url>","Re-fetch the registry index (it may be stale and reference old artifact paths) and retry the install","If you publish the registry, re-upload the archive and republish the index together so entry URL and artifact stay in sync","Retry after a wait for 429/5xx artifact-host errors"],"exampleFix":"# before\n# registry entry: { \"url\": \"https://cdn/old/p-0.2.0.zip\" }  -> 404\nzeroclaw plugin install p\n\n# after\n# republish index with corrected url, then:\nzeroclaw plugin install p --registry https://reg/registry/index.json","handlingStrategy":"retry","validationCode":"// HEAD the archive URL from the registry entry before install:\nlet resp = reqwest::head(&entry.url).await?;\nif !resp.status().is_success() { /* defer install; report stale entry */ }","typeGuard":null,"tryCatchPattern":"// Retry transient 5xx/429 on 'plugin archive returned HTTP' failures; on 404\n// refresh the registry index once and retry, then give up with a clear report.","preventionTips":["Publish index and artifact atomically if you run a registry","Use stable, non-expiring artifact URLs (avoid short-lived signed links)","Verify artifact URLs in registry lint checks"],"tags":["plugin-registry","download","http-status","network"],"backgroundTag":"download-http-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}