Skip to content

fix(installer): preserve skill edits when installation rolls back - #6757

Merged
huangruiteng merged 3 commits into
mainfrom
codex/installer-skill-rollback-20261017
Oct 17, 2026
Merged

huangruiteng merged 3 commits into
mainfrom
codex/installer-skill-rollback-20261017

Conversation

@loopx-agent

@loopx-agent loopx-agent commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

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 to 70f7c651, and the new head needs fresh review.

Scope and ownership

  • Reuse _restore_paths, _snapshot_paths and the existing _skill_mutation_paths list. 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.
  • Recovery folders use .NAME.install-recovery.*/NAME beside 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.
  • Failed installation can retain extra disk contents. Successful installation is unchanged. Filesystem rollback does not reverse an authority database upgrade.
  • The bounded refactor pass keeps existence checks, preservation and error readback in the same existing restore owner, without another recovery layer. This addresses the S10/S12 failure-recovery boundary; it does not qualify the full release or combined fix(skills): preserve scoped Ego continuation through LoopX upgrades #6749 journey.

Validation

Tested head: 1aab86eddb06b66cc8ba25ec49a2eed890de4934. Fixtures are synthetic; filesystem operations use real disposable directories.

Check Result and boundary
Missing-path comparison The same six new regression/control cases on 70f7c651 produce 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.
Complete related regression 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.
Actual filesystem caller install_windows performs 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.
Static and boundary Diff hygiene, DCO, bilingual-RFC staged check, candidate public/private scan and development semantic advisory pass. The advisory found no supported new carriers; that alone is not equivalence proof.
Exact-head premerge Passed: six direct checks and all five selected canaries (local install, packaged install, update, maintainability, full semantic vocabulary). Zero failures, warnings or advisory failures.

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-smoke timeout 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.

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 loopx-agent left a comment

Copy link
Copy Markdown
Collaborator Author

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=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.

Comment thread loopx/windows_install.py Outdated
retained: list[Path] = []
try:
for path, backup in reversed(snapshots):
if path in skill_entries or path.is_dir() or path.is_symlink():

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[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>
@huangruiteng
huangruiteng merged commit a1b8fb3 into main Oct 17, 2026
25 of 35 checks passed
@huangruiteng
huangruiteng deleted the codex/installer-skill-rollback-20261017 branch October 17, 2026 16:28
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.

2 participants