Skip to content

bug(worktree): 自动回收会删除被 index flags 隐藏的未提交修改 [P1] #472

Description

@tt-a1i

解决什么问题

子 Agent 的 worktree 中,如果 tracked 文件被该 worktree 的 index 标记为 assume-unchangedskip-worktree,本地修改可以不出现在 git status 中。OpenPI 的自动回收目前把这类工作区判成没有产出,随后执行不带 --forcegit worktree remove,仍然能够删除修改文件的唯一副本。

这是特定 worktree index 的状态,不是声称这些标记会从父 checkout 自动继承,也不是普通 ignored 文件问题。OpenPI 已单独保留 ignored 文件;缺口是被 index 标记隐藏的 tracked 修改。

价值是什么

自动清理必须保留无法证明可丢弃的内容。“Git status 为空”和“不使用 force”都不足以证明目录里没有需要保留的工作。补齐检查可以避免子 Agent 执行结束后的静默数据丢失。

验证边界

检查日期:2026-09-08。OpenPI 固定为 ed9dbc1018f890fd54375f5371990ddfee8af5df

  • 已执行事实:使用系统 git version 2.50.1 (Apple Git-155),在独立临时 Git 仓库中逐一设置两种标记,执行 OpenPI 清理依赖的 Git 检查和相同的 git worktree remove。两种情况都返回删除成功,修改文件随后不存在。
  • 已执行 OpenPI 生产函数:使用 Node v26.3.0 直接导入上述 checkout 的 extensions/shared/worktree.ts,在第二组独立临时 fixture 中调用 reclaimWorktree(repo, { path, branch, baseSha })。两种标记均得到 removed: true, branchDeleted: true, dirty: false, ignored: false, commits: 0,修改文件和工作区目录实际被删除,分支也不存在。
  • 未验证范围:没有启动完整 OpenPI 子 Agent/Workflow 生命周期,没有调用模型,也没有运行社区插件。
  • 全部文件均为合成 fixture,没有读取或删除真实项目数据。

最小复现

以下命令只创建和操作新建临时目录内的测试仓库;两个 worktree remove 是复现中有意执行的删除动作。保留测试主仓库以便检查结果。

audit_root="$(mktemp -d "${TMPDIR:-/tmp}/openpi-index-flags.XXXXXX")"
git init -q "$audit_root/repo"
git -C "$audit_root/repo" config user.name 'Local Fixture'
git -C "$audit_root/repo" config user.email 'fixture@example.invalid'
printf 'base\n' > "$audit_root/repo/tracked.txt"
git -C "$audit_root/repo" add tracked.txt
git -C "$audit_root/repo" commit -qm fixture
audit_base="$(git -C "$audit_root/repo" rev-parse HEAD)"

for audit_flag in assume-unchanged skip-worktree; do
  audit_wt="$audit_root/$audit_flag"
  git -C "$audit_root/repo" worktree add -q -b "probe-$audit_flag" "$audit_wt" HEAD
  printf 'ONLY COPY OF SYNTHETIC USER CHANGE\n' > "$audit_wt/tracked.txt"
  git -C "$audit_wt" update-index "--$audit_flag" tracked.txt

  git -C "$audit_wt" status --porcelain=v1 --untracked-files=all
  git -C "$audit_wt" ls-files --others --ignored --exclude-standard -z
  git -C "$audit_wt" rev-parse --verify HEAD
  git -C "$audit_wt" symbolic-ref --quiet --short HEAD
  git -C "$audit_root/repo" merge-base "$audit_base" "$audit_base"
  git -C "$audit_root/repo" rev-list --count "$audit_base..$audit_base"
  git -C "$audit_wt" ls-files -v

  git -C "$audit_root/repo" worktree remove "$audit_wt"
  test ! -e "$audit_wt/tracked.txt" && printf '%s: modified file was removed\n' "$audit_flag"
done

观察到的关键结果:

检查 assume-unchanged skip-worktree
status 输出
ignored inventory
HEAD / merge base 等于创建基线 等于创建基线
新增 commit 数 0 0
ls-files -v h tracked.txt S tracked.txt
worktree remove exit 0 exit 0
修改文件仍存在

OpenPI 生产函数的直接结果

对上述等价 fixture,以当前源码直接执行:

const cleanup = await reclaimWorktree(repo, { path: worktreePath, branch, baseSha });

两种标记得到相同的核心回执和独立文件检查结果:

{
  "cleanup": {
    "removed": true,
    "branchDeleted": true,
    "detached": false,
    "commits": 0,
    "dirty": false,
    "untracked": false,
    "ignored": false
  },
  "worktreeExistsAfter": false,
  "modifiedFileExistsAfter": false,
  "branchesAfter": ""
}

当前代码路径

社区实现启发

@narumitw/pi-worktree 的当前实现(MIT,固定 09d0f4d4d8d31f506e6f2478a0060be5f5c31c09)提供了可借鉴的具体机制:

建议借鉴清理前的数据保护机制,不引入另一套 worktree 管理器,也不为了检查而清除用户的 index 标记。

最小验收

  • assume-unchanged 的 tracked 修改在自动回收后文件仍完整保留,回执说明 index 标记造成的不确定性。
  • skip-worktree 的 tracked 修改同样保留;不能简单把所有 skip-worktree 都当成安全 sparse checkout。
  • Index inventory 读取失败、截断或格式无法识别时保留 worktree,不能声称清理成功。
  • 若支持回收正常 sparse checkout,必须单独证明未落盘路径与被隐藏本地内容的边界,不能影响上述保护。
  • 检查不修改 index,不执行 reset、clean、checkout 或强制删除来“修复”状态。
  • reclaimWorktree 补实际临时 Git 仓库回归测试,分别断言文件内容、清理回执与分支状态;现有 ignored/untracked/committed/detached 保护继续通过。
  • bun run checkbun run test 通过。

研究记录:Pi 社区能力研究与候选清单(2026-09-08)。源码证据、运行验证与采用决策分别记录。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions