BigPizzaV3/CodexPlusPlus · error · anyhow::Error
skill id 不能为空
Error message
skill id 不能为空
What it means
validate_skill_id is a path-safety guard for ids used directly in filesystem paths (SSOT dir, backups). It bails on an empty id before the traversal checks; callers like uninstall/restore_backup pass user-supplied ids, so empty input is caught here.
Solutions
- 调用前检查 skill_id 非空(且非纯空白)再执行操作
- 排查 id 来源:从 zip 的 SKILL.md 或仓库清单解析时,manifest 的 name 字段可能为空,需先补齐
- UI 层在 skill 未选中时禁用 uninstall/delete 等按钮
Example fix
// before
state.uninstall(&skill_id)?; // skill_id 可能为 ""
// after
if skill_id.trim().is_empty() {
anyhow::bail!("请先选择要操作的 skill");
}
state.uninstall(&skill_id)?; Defensive patterns
Strategy: validation
Validate before calling
fn ensure_skill_id_present(id: &str) -> Result<(), String> {
if id.trim().is_empty() { Err("skill id 不能为空".into()) } else { Ok(()) }
} Try / catch
match state.uninstall(&skill_id) {
Err(e) if e.to_string().contains("skill id 不能为空") => eprintln!("未选择 skill,忽略操作"),
other => other?,
} Prevention
- UI 在未选中 skill 时禁用相关操作按钮
- 解析 SKILL.md/仓库清单时校验 name 非空并回填 id
- 对所有 skill_id 入参统一走一个入口校验包装
When it happens
Trigger: 任一接受 skill_id 的公开方法传入空字符串 "",例如 uninstall("")、set_enabled("", true)、restore_backup("")、delete_backup("")。
Common situations: 前端列表为空时仍触发了操作;skill 清单里 name 字段缺失或为空串;上游数据未回填 id 就调用接口。
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/cb91bf95f269cb37.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/skills.rs:622
owner,
name,
branch,
subdir,
enabled: repo.enabled,
})
}
fn is_safe_repo_segment(value: &str) -> bool {
!value.is_empty()
&& value
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-'))
}
/// skill id 直接参与拼路径,必须挡住 `..` 和分隔符。
fn validate_skill_id(id: &str) -> anyhow::Result<()> {
if id.is_empty() {
anyhow::bail!("skill id 不能为空");
}
if id == "." || id == ".." || id.contains('/') || id.contains('\\') {
anyhow::bail!("非法的 skill id:{id}");
}
Ok(())
}
/// GitHub trees API 的响应 → 该仓库里的 skill 清单。
///
/// 一个目录只要直接含 `SKILL.md` 就算一个 skill;`content_hash` 用子树里
/// 每个文件的 blob sha 算,远端内容一变哈希就变,不用下载就能判断有没有更新。
pub fn parse_skills_from_tree(repo: &SkillRepo, tree: &Value) -> Vec<RemoteSkill> {
let Some(items) = tree.get("tree").and_then(Value::as_array) else {
return Vec::new();
};
let prefix = if repo.subdir.is_empty() {
String::new()
} else {View on GitHub (pinned to b1ed92e5e4)