BigPizzaV3/CodexPlusPlus · error · anyhow::Error

备份不存在:

Error message

备份不存在:{}

What it means

restore_backup takes a backup_id and immediately runs validate_skill_id on it; this bail fires when the id is empty. Since the id is used to build the backups_dir path, an empty value would resolve to the backups directory itself, so it is rejected before any filesystem check.

Solutions

  1. 先调用列出备份的接口(或直接检查 backups_dir/<backup_id> 是否存在)确认 backup_id 有效,再调用 restore_backup
  2. 如果备份已成功恢复过一次,目录已不在 backups 下,无需也无法再次恢复;目标 skill 已存在时会改报“已经装着了”
  3. 检查是否传错字符串:backup_id 格式为 <skill_id>__<timestamp> 之类,由 split_backup_id 约定,需原样传入
  4. 确认运行环境指向的根目录(backups_dir 所在)与创建备份时一致

Example fix

// before
state.restore_backup("my-skill__20260101"); // 目录可能已被恢复掉
// after
let backup_path = state.backups_dir().join("my-skill__20260101");
if backup_path.is_dir() {
    state.restore_backup("my-skill__20260101")?;
}
Defensive patterns

Strategy: validation

Validate before calling

fn backup_exists(backups_dir: &Path, backup_id: &str) -> bool {
    backups_dir.join(backup_id).is_dir()
}
if !backup_exists(&state.backups_dir(), backup_id) { return; }

Try / catch

match state.restore_backup(backup_id) {
    Err(e) if e.to_string().contains("备份不存在") => eprintln!("备份 {} 不存在,跳过", backup_id),
    other => other?,
}

Prevention

When it happens

Trigger: 调用 SkillsState::restore_backup(backup_id) 时传入的 backup_id 在 backups_dir 下没有对应的目录(backup.is_dir() 为 false),例如传入了从未创建过的备份 id、备份已被恢复(目录被移走)或已被 delete_backup 删除。

Common situations: 重复恢复同一个备份(第一次成功后目录已被移回 source_dir);手工清理或迁移 backups 目录后 id 失效;拼写错误的 backup_id;换机器或换 profile 后备份目录不存在。

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

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

            let manifest = path.join(SKILL_MANIFEST_FILE);
            let (name, _) = read_skill_manifest(&manifest, &skill_id);
            backups.push(SkillBackup {
                id: id.to_string(),
                skill_id,
                name,
                backed_up_at,
            });
        }
        backups.sort_by(|a, b| b.id.cmp(&a.id));
        backups
    }

    /// 从备份恢复:目录移回 SSOT 并重新建链。
    pub fn restore_backup(&self, backup_id: &str) -> anyhow::Result<SkillsState> {
        validate_skill_id(backup_id)?;
        let backup = self.backups_dir.join(backup_id);
        if !backup.is_dir() {
            anyhow::bail!("备份不存在:{}", backup.display());
        }
        let (skill_id, _) = split_backup_id(backup_id);
        validate_skill_id(&skill_id)?;
        let destination = self.source_dir.join(&skill_id);
        if destination.exists() {
            anyhow::bail!("{skill_id} 已经装着了,先卸载再从备份恢复");
        }
        std::fs::create_dir_all(&self.source_dir)
            .with_context(|| format!("创建目录失败:{}", self.source_dir.display()))?;
        std::fs::rename(&backup, &destination)
            .with_context(|| format!("恢复到 {} 失败", destination.display()))?;

        let (name, description) =
            read_skill_manifest(&destination.join(SKILL_MANIFEST_FILE), &skill_id);
        {
            let _guard = self.state_lock.lock().unwrap();
            let mut state = self.load_state_unlocked();
            state.installed.insert(

View on GitHub (pinned to b1ed92e5e4)