BigPizzaV3/CodexPlusPlus · error · anyhow::Error

skill 在仓库中的路径不能为空

Error message

skill 在仓库中的路径不能为空

What it means

extract_skill_subtree trims leading/trailing slashes from repo_path and bails when the result is empty. The parameter selects which subtree of the repo zip to extract; an empty value would mean extracting the whole repo (or nothing meaningful), so it is rejected before opening the archive.

Solutions

  1. 确保传入 repo_path 为仓库内 skill 子目录路径,如 "skills/my-skill"
  2. 若 skill 位于仓库根目录,不应走 subtree 提取路径,改用根目录安装流程
  3. 提交前对 subdir 做非空校验(trim 后)。

Example fix

// before
extract_skill_subtree(&zip_bytes, &repo.subdir, &dest)?; // subdir 可能为 ""
// after
let rp = repo.subdir.trim().trim_matches('/');
if rp.is_empty() {
    anyhow::bail!("subdir 不能为空");
}
extract_skill_subtree(&zip_bytes, rp, &dest)?;
Defensive patterns

Strategy: validation

Validate before calling

fn valid_repo_path(subdir: &str) -> bool {
    let t = subdir.trim().trim_matches('/');
    !t.is_empty()
}

Try / catch

match extract_skill_subtree(&zip, repo_path, &dest) {
    Err(e) if e.to_string().contains("路径不能为空") => eprintln!("subdir 未配置,无法按子目录安装"),
    other => other?,
}

Prevention

When it happens

Trigger: 调用 extract_skill_subtree(zip_bytes, repo_path, destination) 时 repo_path 为空串、纯空格或仅由 '/' 组成(trim_matches('/') 后为空)。

Common situations: SkillRepo 的 subdir 字段为空却调用了按子目录安装的流程;从 GitHub trees API 响应取 path 时字段缺失得到空串;把根仓库(skill 在仓库根)的包直接塞进子树提取逻辑。

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/65035b2a0ac970a2. Report an issue: GitHub.

Appendix: source

Thrown at crates/codex-plus-core/src/skills.rs:776

    match std::fs::read_to_string(manifest) {
        Ok(text) => parse_skill_frontmatter(&text, fallback_id),
        Err(_) => (fallback_id.to_string(), String::new()),
    }
}

/// 从仓库 zip 里只解出 `repo_path` 这一棵子树。
///
/// GitHub 的 zip 统一带一层 `repo-ref/` 前缀,`zip_entry_relative_path` 会剥掉它
/// 并同时挡住路径逃逸。符号链接条目一律拒绝——与 codex 内置 skill-installer 的
/// `_validate_skill` 一致,避免装进来的 skill 指到仓库外面。
pub fn extract_skill_subtree(
    zip_bytes: &[u8],
    repo_path: &str,
    destination: &Path,
) -> anyhow::Result<()> {
    let repo_path = repo_path.trim_matches('/');
    if repo_path.is_empty() {
        anyhow::bail!("skill 在仓库中的路径不能为空");
    }
    let prefix = format!("{repo_path}/");
    let mut archive =
        zip::ZipArchive::new(Cursor::new(zip_bytes)).context("skill 仓库压缩包无法解析")?;
    let mut wrote_manifest = false;

    for index in 0..archive.len() {
        let mut file = archive
            .by_index(index)
            .with_context(|| format!("读取压缩包条目 {index} 失败"))?;
        if file.is_symlink() {
            anyhow::bail!("skill 中不允许包含符号链接:{}", file.name());
        }
        let Some(relative) = crate::plugin_marketplace::zip_entry_relative_path(file.name()) else {
            continue;
        };
        // zip 内部的分隔符恒为 '/',但上面拿回来的是 PathBuf,在 Windows 上
        // to_str() 会渲染成 '\',跟用 '/' 拼出来的 prefix 永远匹配不上——结果就是

View on GitHub (pinned to b1ed92e5e4)