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>
where

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Rebuild the plugin archive with a standard tool (cargo zip, `zip -r`) so every entry has a normal relative path
  2. Inspect the archive to find the degenerate entry: `unzip -l plugin.zip` and look for blank/root entries
  3. Verify the archive's sha256 against the registry to rule out corruption in transit
  4. 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

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


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/0f4c5e082c5ad92d. Report an issue: GitHub.