zeroclaw-labs/zeroclaw · error · anyhow::Error
plugin archive entry has no parent: {}
Error message
plugin archive entry has no parent: {} What it means
During safe extraction, a zip entry resolved to an output path that has no parent directory (out_path.parent() returned None). That only happens for a path that is a root/componentless target — a degenerate archive entry (e.g. name "" or "/"), since even "file.txt" yields a parent. extract_zip_safe_with_limit bails with the offending entry name.
Source
Thrown at src/plugin_registry.rs:189
dest: &Path,
max_extracted_bytes: u64,
) -> Result<PathBuf>
where
R: Read + Seek,
{
let mut archive = zip::ZipArchive::new(reader)?;
std::fs::create_dir_all(dest)?;
let mut extracted_bytes = 0_u64;
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let enclosed = enclosed_zip_path(file.name(), &file)?;
let out_path = dest.join(enclosed);
if file.is_dir() {
std::fs::create_dir_all(&out_path)?;
continue;
}
let Some(parent) = out_path.parent() else {
bail!("plugin archive entry has no parent: {}", file.name());
};
if extracted_bytes.saturating_add(file.size()) > max_extracted_bytes {
bail!("plugin archive exceeds extracted size limit of {max_extracted_bytes} bytes");
}
std::fs::create_dir_all(parent)?;
let mut out = File::create(&out_path)?;
copy_zip_entry_capped(
&mut file,
&mut out,
&mut extracted_bytes,
max_extracted_bytes,
)?;
}
Ok(dest.to_path_buf())
}
fn enclosed_zip_path<R>(raw_name: &str, file: &zip::read::ZipFile<'_, R>) -> Result<PathBuf>
whereView on GitHub (pinned to 88bb9c8533)
Solutions
- Rebuild the plugin archive with a standard tool (cargo zip, `zip -r`) so every entry has a normal relative path
- Inspect the archive to find the degenerate entry: `unzip -l plugin.zip` and look for blank/root entries
- Verify the archive's sha256 against the registry to rule out corruption in transit
- If you control the packer, filter out empty/root entries before writing the zip
Example fix
# before: zip contains an entry named "" (or "/") # after: rebuild cleanly cd plugin-root && zip -r ../p-0.1.0.zip . -x '*.DS_Store' # republish entry url+sha256 for the new archive
Defensive patterns
Strategy: try-catch
Validate before calling
// Reject degenerate entries before extraction:
for i in 0..archive.len() {
let f = archive.by_index(i)?;
if f.enclosed_name().is_none() || f.name().is_empty() { /* reject archive */ }
} Try / catch
// Catch 'plugin archive entry has no parent', quarantine the archive, and // rebuild it with a standard zipper — retrying the same bytes is pointless.
Prevention
- Build plugin zips with mainstream tools, not ad-hoc scripts
- Validate archives in CI with `unzip -t` before publishing
- Prefer re-downloading from the publisher over patching broken zips by hand
When it happens
Trigger: Extracting a plugin archive containing an entry whose (sanitized) path is empty or root-level with no directory component. Reached from extract_zip_safe during plugin install after the archive passes the download checks.
Common situations: Hand-rolled zip scripts appending an empty-named entry; corrupted zip metadata (zeroed name field); zip tools emitting odd directory markers; a fuzzed/maliciously crafted archive.
Related errors
- plugin archive exceeds extracted size limit of {max_extracte
- plugin archive contains unsafe path: {raw_name}
- the public plugin registry is not populated yet; use --regis
- plugin registry returned HTTP {status} for {registry_url}
- plugin archive returned HTTP {status} for {url}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/0f4c5e082c5ad92d.
Report an issue: GitHub.