Repository navigation
fix(installer): preserve skill edits when installation rolls back - #6757
Conversation
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
动机
需要在安装失败后保住本地技能及定制内容的 Windows 用户。
旧回滚会直接覆盖安装期间的修改;本改动先保留中断内容再恢复旧版本,但删除旧技能后复制失败时,当前实现反而不能恢复原技能。
目录、链接和替换文件的保全用例通过;缺失技能会让回滚提前退出,实际安装失败后原技能仍然丢失。
本次只评审安装器失败恢复;未完成原生 Windows/PowerShell、实际安装升级、数据库回退或 6749 组合验收。
改动思路
延伸现有 _restore_paths 即可覆盖真实安装调用;Python 是既有平台 IO adapter,状态与效果没有第二个决策 owner。
在既有 Windows 文件系统回滚 owner 内保全中断技能,并回传恢复位置;不新增控制面或通用恢复框架。
先把当前条目移到同级恢复目录,再复制旧快照,能避免清理临时快照时一并丢掉用户修改。仅按目录或链接类型保护会漏掉被替换成普通文件的技能,因此复用既有路径清单有必要。不过“该路径属于技能清单”与“该路径现在存在”是不同事实;后者缺失时,应直接恢复原快照,而非创建一个必然失败的停泊操作。
以下对照是独立文件系统执行结果,绑定 70f7c651099f9a6af936e8aa16bbcf3e65208771;不是原生 Windows 验收:
| 相同输入与独立预期 | base ad5c2b57 |
当前 head |
|---|---|---|
| 从未创建的技能,不应阻断后续指针恢复 | 通过 | FileNotFoundError,后续恢复中止 |
| 原技能已删除,必须恢复旧字节 | 通过 | FileNotFoundError,旧技能未恢复 |
| 实际安装入口中复制技能失败,清理快照后原技能仍在 | 通过 | 原技能丢失 |
| 普通 launcher 文件恢复对照 | 通过 | 通过 |
具体改动
本 PR 没有独立的书面规格;以原有安装失败恢复契约及已复现的数据保全问题为依据,并对齐路线图 S10/S12 的可靠恢复目标。完整两文件差异为运行时代码 +47/-9、既有测试 +141。目录保全、文件替换、相对与绝对链接、再次恢复失败和 launcher 兼容行为有实际覆盖;“缺失目标仍恢复旧快照”这一兼容条件尚未满足。原生 Windows 与 6749 组合均保留未测,不由作者声明代替。
关键代码讲解
_restore_paths现在返回保留下来的路径,并在覆盖前移动目录、链接和已声明技能条目。异常分支保留此前已停泊的路径。但第 394 行的集合成员判断直接进入path.replace,没有确认该条目仍存在,因此尚未执行到第 410 行的旧快照复制就会失败。install_windows在原快照前派生一次技能路径清单,并把同一清单交给失败回滚;这复用已有 owner。它随后离开临时快照上下文,因此失败恢复不能依赖该目录以后仍存在。- 新增的目录、链接、普通文件与恢复失败测试证明了有内容可停泊时的行为;它们都先创建当前条目,没有覆盖复制失败后没有当前条目的合法输入。补入缺失路径测试即可覆盖此漏洞,无须扩大为新的恢复系统。
对主干的风险
[P1] 缺失的技能路径会中止整个回滚,并可能丢失原技能。 _install_skills 会先删除原技能目录,再复制候选。若复制在目标创建前失败,该技能以及尚未轮到的新技能都可能不存在。新条件仍尝试重命名不存在的路径,抛出 FileNotFoundError,跳过原快照及后面的恢复;临时目录清理后,原技能也不再有可用副本。实际安装入口的隔离用例已观察到原 SKILL.md 丢失,base 同一用例能恢复。
只对实际存在的条目(含悬空链接)执行停泊;路径缺失仍恢复其旧快照,并继续恢复其余快照,不能直接 continue 跳过旧数据。
最小复现可在临时目录中建立原技能与快照,删除技能目录模拟复制前失败,再调用 _restore_paths(snapshots, skill_paths=[skill]);应恢复旧文件并继续恢复其他条目。还应覆盖 (missing_path, None),以及普通 launcher 对照。修改后重跑 uv run --extra test python -m pytest -q -rs tests/test_windows_install.py tests/test_self_update_runtime_activation.py,并通过真实失败入口检查临时快照清理后的文件。
当前仓库两文件测试 59 通过、6 跳过;独立四例 base 4 通过,head 1 通过、3 失败。平台入口探针只在自有临时目录运行:适配了平台准入,替代候选准备、候选校验与 authority upgrade;真正被审查的安装变更、删除、快照、复制失败、恢复及清理都使用实际文件系统。这不等同原生 Windows/PowerShell 执行。开发语义 advisory 已先执行;标准 premerge 的六个直接检查及五个目录检查中的四个通过,install-local-smoke 在既定 120 秒预算超时,总体 exit=1。超时尚无同基线归因,作为未解验证缺口保留,不据此声称作者引入另一缺陷,也未调高预算。语义全树检查通过仍不能消除上面的反例。未查询、轮询或等待 CI。
我的整体评价
本改动方向和代码归属合理,补上“文件替换也须保护”有实际价值,额外磁盘保留的代价已披露;但完整恢复结果仍回归,长期工作连续性与用户错误恢复均不能判为合格。在既有 Windows 文件系统回滚 owner 内保全中断技能,并回传恢复位置;不新增控制面或通用恢复框架。 有界的前瞻检查保留同一恢复 owner 和自动派生清单,没有必要另加模块;只需修复缺失路径并补完整反例。该修复合格后再由 6749 集成并独立验证,不能提前把依赖关系当作已交付结果。未新增共享词汇或权限边界;也不要求为此改写无关语言或扩大范围。
English verdict: REQUEST_CHANGES — 70f7c651099f9a6af936e8aa16bbcf3e65208771. Declared-but-missing skill paths abort rollback before snapshot restoration; a real disposable-filesystem installer failure loses the original skill. The matched four-case probe passes 4/4 on base and 1/4 on head. Existing tests pass 59 with 6 platform skips. Restore absent targets from their snapshots without skipping later restores, then requalify the caller. No native Windows or combined 6749 qualification is claimed.
| retained: list[Path] = [] | ||
| try: | ||
| for path, backup in reversed(snapshots): | ||
| if path in skill_entries or path.is_dir() or path.is_symlink(): |
There was a problem hiding this comment.
[P1] Restore snapshots when a declared skill path is missing
A declared entry may be absent because it has not been created yet, or because _install_skills removed its old directory before copytree failed. Membership in skill_entries still enters this branch, so path.replace(recovery) raises before the old snapshot and remaining entries are restored. When install_windows leaves its temporary snapshot context, the previous skill can be lost entirely. An independent disposable-filesystem installer copy-failure case restores the original skill on base ad5c2b5 but loses it on this head; the four-case matched probe is 4/4 on base and 1/4 here. Park only an entry that actually exists (including dangling symlinks); for an absent entry still restore its old backup and continue other snapshots. Add absent/new and removed/existing cases plus the real failure caller, rather than skipping missing entries wholesale.
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
Problem and resulting behavior
A failed skill installation must restore the previous installation without deleting the only copy of a customization made during the attempt. The Windows installer now keeps actual interrupted skill entries in sibling recovery folders, outside temporary snapshot cleanup, before restoring the old snapshots. Recovery reports those paths even if a later restore fails; ordinary launcher-file rollback retains its existing behavior.
A declared skill path can also be absent: installation deletes the old directory before copying the replacement. The previous candidate tried to park that nonexistent entry and aborted rollback, losing the old skill after temporary cleanup. The current head restores its snapshot and continues restoring later entries. A genuinely absent entry needs no recovery copy; dangling links still count as existing entries and are retained.
Basis: the existing installer rollback contract at
ad5c2b57605273cc567d85f8f3ee39ba6f11f023, the original customization-loss reproduction, and the exact-head missing-path counterexample. This updates the same author proposal; the earlier review applies to70f7c651, and the new head needs fresh review.Scope and ownership
_restore_paths,_snapshot_pathsand the existing_skill_mutation_pathslist. The Python Windows filesystem adapter owns this IO behavior; there is no new capability, shared state vocabulary, authority rule or configuration owner. The separate skill-publication repair in fix(skills): preserve scoped Ego continuation through LoopX upgrades #6749 retains its own scope and qualification..NAME.install-recovery.*/NAMEbeside the original entry. They may contain installer writes as well as user edits; inspect them before adopting or deleting them. Relative links retain their literal targets and recover their meaning when returned to their original location.Validation
Tested head:
1aab86eddb06b66cc8ba25ec49a2eed890de4934. Fixtures are synthetic; filesystem operations use real disposable directories.70f7c651produce 5 failures / 1 pass. They cover missing targets with and without snapshots or parent directories, later pointer restoration, actual installer copy failure, and a dangling-link control. The complete candidate suite passes all six.uv run --extra test python -m pytest -q -rs tests/test_windows_install.py tests/test_self_update_runtime_activation.py: 65 passed, 6 platform skips on macOS arm64 / Python 3.14.8. Existing directory, regular-file replacement, absolute/relative link, restore-failure and launcher controls are retained.install_windowsperforms release copying, snapshotting, old-skill deletion, injected copy failure, rollback and temporary cleanup. Previous skill/launcher/pointer bytes survive cleanup, the candidate remains available, and the original copy error is preserved. On this host only the module-local Windows admission is adapted; candidate validation and authority upgrade are substituted. This is not native Windows/PowerShell execution or an installed-host upgrade.The historical preservation comparison against the original base remains applicable: five lost-customization cases and one launcher control, followed by the declared-path fix for two regular-file replacements. The missing-path failures above are separate retained counterevidence, not overwritten by those earlier green cases. The first diagnostic failure hook was narrowed to candidate copying before the final matched comparison.
The reviewer’s earlier 120-second
install-local-smoketimeout remains an observed, unattributed validation failure. The current exact-head run passed that same configured smoke in 29.872 seconds, within its existing budget; this does not explain or erase the earlier timeout. Native Windows/PowerShell, an actual installation, combined #6749 adoption, full hosted CI and fresh exact-head review remain separate qualifications. No merge, installed-host upgrade or manual workflow dispatch was performed.Frontend / visual evidence
No graphical presentation changes. The existing PowerShell installer is the user entry point; no new frontend or Lark capability is introduced. First-viewport evidence is N/A.
中文说明
安装回滚既要保留中断期间的真实内容,也要恢复旧版本。已删除、尚未复制出来的技能路径没有内容可停泊,应恢复旧快照并继续后续恢复;悬空链接仍是现存条目,必须保留。新增反例在旧候选上 5 失败、1 对照通过;修复后两套完整相关回归 65 通过、6 项平台跳过。实际临时文件系统安装调用已验证复制失败及快照清理后的旧文件恢复,不能冒充原生 Windows 或实际升级验收。旧评审反例与超时记录保留,新头等待独立评审。
Author declaration
Written by OpenAI Codex, a GPT-6 model agent. All branch commits carry DCO sign-offs. This is a normal PR for the completed author proposal; validation and merge authority remain separate.