BigPizzaV3/CodexPlusPlus · error · anyhow::Error
压缩包条目路径为空
Error message
压缩包条目路径为空
What it means
safe_relative_path finishes by rejecting a result whose OsStr is empty: the entry path contained no usable components (e.g. a bare directory entry or an all-'.' path). Writing it would produce an invalid destination path, so it is rejected.
Solutions
- 检查 zip 包内条目名,剔除空名或纯 './' 的条目后重新打包上传
- 换用标准打包工具(如 zip -r 或 GitHub 官方 zip 产物)重新生成压缩包
- 若确需允许该条目,在代码中把空路径条目直接 skip 而不是报错(需自行评估风险)
Example fix
// before
if relative.as_os_str().is_empty() {
anyhow::bail!("压缩包条目路径为空");
}
// after
if relative.as_os_str().is_empty() {
tracing::warn!("跳过无有效路径的压缩包条目");
return Ok(None); // 调用方按 Option<PathBuf> 处理
} Defensive patterns
Strategy: validation
Validate before calling
fn is_safe_entry_name(name: &str) -> bool {
let mut has_normal = false;
for comp in std::path::Path::new(name).components() {
match comp {
std::path::Component::Normal(_) => has_normal = true,
std::path::Component::CurDir => {}
_ => return false,
}
}
has_normal
} Type guard
if !is_safe_entry_name(entry.name()) { continue; } Prevention
- 只用标准工具(zip -r、git archive、GitHub 官方 zip)打包技能仓库
- 安装第三方技能前用 unzip -l 检查条目名
- 定期更新 skills.rs 以获得最新的路径安全修复
When it happens
Trigger: 调用 extract_skill_subtree 解压一个 GitHub 仓库 zip,其中某个条目名(name)为空、仅为 '/'、或所有组件都是 CurDir('.'),safe_relative_path 递归处理每个 Component 后 relative 为空。
Common situations: 第三方仓库打包工具生成了畸形条目(空文件名或纯目录占位条目);手工构造或被篡改的 zip 包;非标准 zip 工具产出的以 ./ 前缀开头的条目被逐段剥光后只剩空路径。
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/0598b57fc5ed0b4c.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/skills.rs:843
}
if !wrote_manifest {
anyhow::bail!("{repo_path} 下没有 SKILL.md,不是一个有效的 skill");
}
Ok(())
}
fn safe_relative_path(value: &str) -> anyhow::Result<PathBuf> {
let mut relative = PathBuf::new();
for component in Path::new(value).components() {
match component {
std::path::Component::Normal(part) => relative.push(part),
std::path::Component::CurDir => {}
_ => anyhow::bail!("压缩包条目越界:{value}"),
}
}
if relative.as_os_str().is_empty() {
anyhow::bail!("压缩包条目路径为空");
}
Ok(relative)
}
pub fn repo_zip_url(repo: &SkillRepo) -> String {
format!(
"https://codeload.github.com/{}/{}/zip/refs/heads/{}",
repo.owner, repo.name, repo.branch
)
}
pub fn repo_tree_url(repo: &SkillRepo) -> String {
format!(
"https://api.github.com/repos/{}/{}/git/trees/{}?recursive=1",
repo.owner, repo.name, repo.branch
)
}
View on GitHub (pinned to b1ed92e5e4)