Skip to content

Preserve workflow skill backups after failed publication - #6211

Merged
huangruiteng merged 1 commit into
loopx-project:mainfrom
Duang777:codex/fix-workflow-skill-install-backup-loss-20261011
Oct 11, 2026
Merged

huangruiteng merged 1 commit into
loopx-project:mainfrom
Duang777:codex/fix-workflow-skill-install-backup-loss-20261011

Conversation

@Duang777

Copy link
Copy Markdown
Collaborator

Summary

  • preserve the previous workflow skill install when publication and restoration both fail
  • refuse stale .previous.* backups before the unchanged fast path or a new replacement
  • cover interrupted installs with and without a live target

Root cause

_install_one_skill deleted its backup in finally. If publishing the staged skill and restoring the previous install both failed, cleanup removed the only recoverable copy. A later process also ignored backups created under another PID and could continue from incomplete recovery state.

Validation

  • Red: the publish and restore failure test ended with FileNotFoundError; both stale-backup cases failed to raise.
  • Green: tests/test_workflow_skill_install.py passes, 28 tests total.
  • python -m ruff check loopx/workflow_skill_install.py tests/test_workflow_skill_install.py
  • python scripts/generate_project_registry_io_manifest.py --check
  • python scripts/generate_semantic_inventory.py --changed-from HEAD
  • loopx check --scan-path loopx/workflow_skill_install.py --scan-path tests/test_workflow_skill_install.py
  • Real CLI install reported ready=true, a repeated install reported only unchanged, and uninstall reported ok=true.
  • loopx canary premerge --from-git-diff --git-diff-base upstream/main passed all 5 selected canaries.

Sibling sweep

Reviewed configuration backup, local state migration, static-site publication, and OpenViking history export. Those paths keep backups until publication succeeds or fail closed on stale recovery state. This PR does not change them.

uv.lock is unchanged.

Preserve the previous install when both skill publication and rollback fail. Refuse stale backups before a later install can overwrite recoverable state.

Signed-off-by: Duang777 <duangjl007@gmail.com>
@Duang777

Copy link
Copy Markdown
Collaborator Author

@loopx-agent review

@loopx-agent loopx-agent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewer: model_agent | gpt-6.1-sol | OpenAI | runtime_reported | reasoning_effort=xhigh

APPROVE — 未发现阻断项。本结论绑定 917d76406af4cba7e9820135b3c8c823628084c6,独立核验完整 base→head 和失败后的实际恢复。

动机

升级托管 workflow skill 的操作者,在新目录发布失败且旧目录恢复也失败时,需要保住仍可恢复的旧技能。
此前异常后的 finally 清理会删除唯一旧备份,下一次安装又可能忽略其它进程留下的备份。现在旧副本保留,带未处理备份的安装会明确拒绝;操作者检查并恢复后可以继续安装。
真实 CLI 路径实测双重失败后旧字节保留、重试零内容改动,人工恢复后安装、幂等重装和卸载均成功。
本 PR 只修复已有 global workflow installer 的恢复安全,不自动选择备份、改写项目 Goal、扩大主机权限或重构多技能安装事务。

改动思路

发布和恢复仍使用既有目录替换与文件锁;改变的是旧副本的删除时机。成功发布后可以删备份,异常退出时不能删最后一份可恢复数据。残留备份本身就是文件系统事实,不需要额外 journal、状态字段或新 capability。针对同一个目标名的检查在 unchanged 快路径之前执行,使“live 文件碰巧相同”也不能掩盖未处理恢复现场。

旧副本保存和恢复现场拒绝都属于现有 workflow installer 的文件系统不变量;Python 在这里是既有本地 IO owner,没有新增通用控制面决策源。
当前 PR 的边界是保留失败发布的旧目录,并阻止该目标在残留备份未处理前再次替换;正常安装、读回与卸载复用原路径。
比较了 project-skill delivery、configuration/local-state migration 与 history export 的备份边界。它们的事务范围和恢复 owner 不同,没有理由为本次三行清理调整引入统一抽象或合并独立策略。相邻整理无需扩大本 PR。

具体改动

基线为 be15bf4ffb3bf354fb255853a96f14b047874bb8,2 个文件 +67/-3:生产文件 +10/-3,测试 +57,无生成物、安装 payload 文本或 uv.lock 变化。

独立依据为修改前 docs/guides/installing-loopx.md,spec_revision=be15bf4ffb3bf354fb255853a96f14b047874bb8,以及旧版本中实际可复现的最后副本丢失。验收映射:Host Command Surfaces 已实现,原 CLI 安装与 revision/digest readback 继续工作;Recovery material preservation 已实现,既有保留 sole copies/冲突需明确处理的原则延伸到本 installer 的残留备份;Skill Discovery Scope 保持不变,文件安装仅改变发现路径,不授予运行或外部操作权限。

关键代码讲解

  • _install_one_skill(workflow_skill_install.py:169)先枚举 .<target>.previous.*,命中就带路径报错;检查在 hash 相等返回之前。temporary 仍在 finally 清理,backup 清理由异常分支之后的正常控制流执行。publish/restore 双故障会抛出异常且保留旧 backup;publish 失败、restore 成功仍恢复原 live 目录。
  • workflow_skill_install(同文件 :274)复用 _exclusive_install_lock 和固定 packaged skill 列表,再生成 $loopx entry、写 revision/digest receipt 并独立读回。恢复前拒绝不会伪造新的成功 manifest;这是已有整体调用者,不是只有 helper 能到达的补丁。
  • handle_workflow_skills_command(cli_commands/workflow_skills.py:61)维持显式 install/uninstall/dry-run 分流;安装文件没有获得执行 Goal 或合并 PR 的权限。人工选择恢复材料后走同一 CLI 即可重试。

对主干的风险

最强反例是实际 CLI 在相同 live 内容下绕过备份,或一个不相关的备份错误阻断全部目标。独立故障脚本经 loopx.cli.main workflow-skills 使用真实临时文件系统,仅对 publish/restore 两次 Path.replace 注入 IO 失败。基线旧备份消失且重试覆盖现场;head 旧字节保持,重试明确 RuntimeError 且文件摘要无变化。移回旧目录后,head install 成功、下一次全为 unchanged、uninstall 成功,证明拒绝之后有真实恢复而不只是一条 blocker。

范围反例也实际执行:同 root 的无关技能备份不阻塞 packaged 安装且被保留;安装后新出现的另一 packaged skill 备份会阻止下一轮;相同 live 内容不能逃过检查;dry-run 不执行替换。命中的备份要由操作者检查,这是防止数据丢失的必要恢复步骤,没有新增重复确认或手工同步字段。

3 个新回归在不改生产代码的旧基线上全部失败:双故障后的 FileNotFoundError,以及有/无 live 的残留备份未拒绝。当前聚焦 installer、discovery 与 packaged metadata suite 63/63 通过,基线同套既有测试 60/60;Ruff、registry IO manifest check、semantic advisory 均通过。premerge 的 5 项 direct 与 5 项选中 canary 通过,包括实际 packaged install、update、maintainability 和完整 semantic drift;没有 failure、skip 或 manual hold。未查询远端 CI,没有读写操作者真正的技能目录或发布私有 evidence。

这是默认安全行为的有意修复,PR 描述和新回归明确旧缺陷与新拒绝;不是新的 opt-in 策略,也未改变自动加载的指令内容。错误文本中“requires inspection”是实际强制拒绝,未将硬义务称作 guidance。未增加 shared vocabulary;检查本地既有备份事实,未创建 authority 生命周期。没有前端/Lark companion 变化;已有 workflow CLI 就是受影响安装入口。

我的整体评价

持续工作与使用体验均为 improved:升级异常之后保留恢复选项,纠正现场后继续使用,而不是永久丢失旧副本。保留既有文件锁和 manifest/hash readback 有实际消费者;独立 project-skill 事务不应强行复用成新的泛化安装框架。此改动与最后可恢复副本丢失的风险相称,生产差异很窄且可回退。

无阻断 finding。尚未证明进程崩溃后的自动恢复、恶意并发 writer 或所有原生 Windows 文件锁语义;本 PR 也不承诺它们。异常保留材料并主动拒绝重试的边界已通过真实 IO 及完整安装恢复链验证。备份究竟对应哪个意图仍需人工检查,避免用猜测自动覆盖。批准不等于 merge 权限,维护者仍负责合并这项 runtime 变更。

English verdict: APPROVE

The exact-head fix preserves the previous skill after publication and restoration both fail, and refuses a stale recovery backup before replacement or the unchanged fast path. Real CLI/filesystem tests demonstrate baseline data loss, head preservation, scoped refusal, and successful manual recovery followed by install/reinstall/uninstall. Local checks pass; remote CI was not consulted. No new authority or automatic recovery is granted.

@huangruiteng
huangruiteng merged commit a5d6d25 into loopx-project:main Oct 11, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants