BigPizzaV3/CodexPlusPlus · error · anyhow::Error
下没有 SKILL.md,不是一个有效的 skill
Error message
{repo_path} 下没有 SKILL.md,不是一个有效的 skill What it means
After extracting all entries, extract_skill_subtree verifies at least one written file was SKILL.md (wrote_manifest flag); otherwise it bails. A subtree without a manifest is not a valid skill and installing it would produce a broken link in the SSOT directory.
Solutions
- 核对 subdir 是否正确指向包含 SKILL.md 的目录,在 GitHub 上确认该路径下确实有 SKILL.md
- 若仓库结构更新,更新 SkillRepo 的 subdir 后重试
- 确认仓库遵循 skill 规范(SKILL.md 位于 skill 根);不规范仓库无法安装
Example fix
// before // subdir = "skills/old-name" 但仓库已改名为 "skills/new-name" extract_skill_subtree(&zip, "skills/old-name", &dest)?; // after extract_skill_subtree(&zip, "skills/new-name", &dest)?; // 含 SKILL.md 的目录
Defensive patterns
Strategy: try-catch
Validate before calling
// 安装前可先核对仓库结构:
// GET https://api.github.com/repos/{owner}/{name}/git/trees/{branch}?recursive=1
// 确认 subdir 下存在 SKILL.md Try / catch
match extract_skill_subtree(&zip, repo_path, &dest) {
Err(e) if e.to_string().contains("SKILL.md") => eprintln!("{} 不是有效的 skill 目录", repo_path),
other => other?,
} Prevention
- 配置 subdir 前在 GitHub 上确认该目录含 SKILL.md
- 仓库结构变更后同步更新 SkillRepo.subdir
- 安装失败时检查 zip 是否完整下载(对比大小或 git tree)
When it happens
Trigger: zip 包中 repo_path 子树内不存在名为 SKILL.md 的文件——通常是 repo_path 填错了子目录,或该目录确实不是 skill 目录。
Common situations: SkillRepo.subdir 指向仓库中非 skill 目录(如文档目录);skill 结构不符合规范(清单用了别的文件名);仓库版本变更后目录结构变化导致旧 subdir 失效;zip 下载不完整。
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- skill 在仓库中的路径不能为空
- 主题包必须是 32 MiB 以内的普通 ZIP 文件
- Dream Skin 主题包为空
- Dream Skin 主题包文件数量必须在 1 到 32 之间
- Dream Skin 主题包超过 32 MiB 限制
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/34cfd09c165fc965.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/skills.rs:828
.with_context(|| format!("创建目录失败:{}", output_path.display()))?;
continue;
}
if let Some(parent) = output_path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("创建目录失败:{}", parent.display()))?;
}
let mut contents = Vec::new();
file.read_to_end(&mut contents)
.with_context(|| format!("读取压缩包条目 {} 失败", file.name()))?;
std::fs::write(&output_path, contents)
.with_context(|| format!("写入 {} 失败", output_path.display()))?;
if inner == SKILL_MANIFEST_FILE {
wrote_manifest = true;
}
}
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)
}View on GitHub (pinned to b1ed92e5e4)