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
- 先调用列出备份的接口(或直接检查 backups_dir/<backup_id> 是否存在)确认 backup_id 有效,再调用 restore_backup
- 如果备份已成功恢复过一次,目录已不在 backups 下,无需也无法再次恢复;目标 skill 已存在时会改报“已经装着了”
- 检查是否传错字符串:backup_id 格式为 <skill_id>__<timestamp> 之类,由 split_backup_id 约定,需原样传入
- 确认运行环境指向的根目录(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
- 恢复前先列出备份并确认 id 存在
- 记住备份是一次性的:恢复成功后即从 backups 目录消失
- 避免手工增删 backups 目录内容
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
- 已经装着了,先卸载再从备份恢复
- Backup source database not found
- 自定义数据库不存在:
- Dream Skin theme backup belongs to a different config.toml
- 找不到原生会话文件
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)