Skip to content

fix(integration-branch): bound every Git read to one budget - #8363

Open
gcl-coder wants to merge 1 commit into
loopx-project:mainfrom
gcl-coder:fix/integration-branch-git-budget
Open

gcl-coder wants to merge 1 commit into
loopx-project:mainfrom
gcl-coder:fix/integration-branch-git-budget

Conversation

@gcl-coder

Copy link
Copy Markdown
Contributor

动机 / Motivation

repository-change-window 与 change-quality 的 Git 读取已经有预算(#8362、#8169),但整合分支这条路径还没有:loopx/capabilities/integration_branch/core.py 的 _git() 是所有读取的唯一入口,configure_integration_branch、integration_branch_status、sync_integration_branch 都经由它执行 rev-parse、check-ignore、worktree list、fetch、merge、update-ref 等命令,底层是一个没有上限的 subprocess.run。

仓库卡在陈旧 index.lock、网络挂载或卡住的 fsmonitor helper 上时,这条调用链既不返回也不报错;放在 hook 或自动化里就会把 Git 操作本身一起挂住。

改动思路 / Approach

_git() 仍是唯一入口,只在这一处加上预算:

  • 每次读取在独立 session 中 Popen,用 communicate(timeout=GIT_READ_TIMEOUT_SECONDS) 等待,预算 10 秒。
  • 超时或收到取消时,调用 main 上已有的共享 retire_owned_process,在同一 deadline 内回收这次读取拥有的进程组、关闭未排空的管道;随后抛 IntegrationBranchError。
  • 正常读取仍返回同样的文本输出、退出码;check=True 时非零退出仍按 stderr 给出原有的错误细节。
  • 10 秒是每次读取重新分配的预算,不是整条命令的共享 deadline。

与 #8169、#8362 一样,这里只复用既有 owner:Git 读取的接口属于本能力,进程与管道的回收属于共享 runtime。

具体改动 / Changes

  • _git():从裸 subprocess.run 改为独立 session 的 Popen + 有界 communicate;超时与取消都先回收再抛出/继续传播。
  • _retire_owned_read():只回收本次读取拥有的会话,清理异常不替换本次读取的 timeout 或取消结果。
  • tests/capabilities/test_integration_branch_git_budget.py:新增三条用例——正常读取保留文本与退出码、永不作答的假 git 在预算内拒绝且心跳停止、Git 无法启动时仍抛 OSError。

对主干的风险 / Risks

  • 本地 tests/capabilities/test_integration_branch.py 加新增三条共 18 项全部通过(18 passed)。
  • 原生 Windows 未执行;Windows 分支的 taskkill 与 Job Object 路径由共享 runtime 承担,其行为已在 fix(change-quality): hold every Git read of the scope to one budget #8169 的注入用例中验证,本 PR 沿用同一入口。
  • 10 秒预算可能拒绝一个合法但极慢的读取(例如超大仓库上的 fetch);这是有意的 fail-closed 边界,调用方会拿到明确错误,而不是无限等待。
  • 不改 plan schema、status/sync 的输出结构、CLI 参数或合并权限。

我的整体评价 / Verdict

范围限定在一处 Git 入口,复用已验证的共享回收路径,正常结果与错误细节都保持不变。原生 Windows 与最慢读取的取舍仍待评审确认。

Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>

@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=max

REQUEST_CHANGES:1 个 P2。默认预算可打断已经登记的临时 worktree,但异常路径没有撤销登记;成功重试后仍留下失效记录。

动机 / Motivation

维护多条已评审分支的开发者需要整合当前 source heads,同时在 Git helper 卡住时得到明确失败。旧同步可能无限等待;新实现让单次 Git 操作超时退出,正常情况下仍生成候选、发布本地整合分支并读回回执。可验证的目标是有界失败后仍能安全继续;本 PR 不改变 plan、分支顺序、source refs 或远端/PR 权限,临时 worktree 清理仍是这个目标的一部分。目前尚缺超时后撤销本次 candidate 登记的恢复路径。

改动思路 / Approach

唯一 Git 入口 _git 保持既有文本和错误处理,并将 communicate 等待限制到每次 10 秒;共享 process_runtime.retire_owned_process 负责已拥有进程组和管道的有界回收。这是既有 Python Git adapter 的 IO 边界,不是新增的 Core 决策能力。与 change_quality.scope、并行 #8362 相比,进程回收 owner 已复用,文本/bytes 与能力异常不必强行合成新框架。

但这里的 _git 同时执行 fetch、merge、worktree add/remove、reset 和 update-ref,并不都是读取。当前切片必须把新超时接入已有写操作的失败清理;仅杀进程不等于撤销 Git 已完成的登记。无需扩大成新事务系统:在既有 _build_candidate 里补本次 candidate 的有界清理并保留原异常,即可完成这个切片。不修改 plan schema、source refs、远端或 PR 权限。

具体改动 / Changes

全 PR 为 2 文件,+153/-6;生产文件 +62/-6,新增测试 91 行。规范依据 loopx/capabilities/integration_branch/README.md,不可变版本 c63a8f9cfc023ff22f39db5cb8b734da7653e83d,原文未被本 PR 改写。configure 的 preview-first 和 ignored plan、boundary 的不 push/不改 source branch 均通过真实 CLI;preview-and-sync 的临时 worktree 清理在下面反例中未满足,需修复后重验。

关键代码讲解

  1. _git L47 将每次 Git 操作放入独立 POSIX session,正常返回维持 UTF-8 replace 和原非零诊断。L73–79 的新 TimeoutExpired 分支回收进程后立即抛 IntegrationBranchError;它也可能中断已有写操作,不能只按读取失败理解。
  2. _retire_owned_read L30 复用共享回收函数,不把清理异常替换成成功结果。这个边界能限制等待,但并不知道 Git 是否已在 common dir 中登记 worktree。
  3. _build_candidate L605 先执行 worktree add,直到其返回后才把 added 设为 True。真实 post-checkout hook 是登记完成之后运行的;此时被新预算中断,finally 看到 added=False,跳过 worktree remove,却删除临时目录,留下 .git/worktrees 中指向不存在目录的条目。

对主干的风险 / Risks

[P2] 超时后清理本次 worktree 登记

独立真实 Git/公开 CLI 反例:建立 main、feature-a 和 ignored plan,先同步;推进 feature-a,再安装内容为 #!/bin/sh 加 sleep 11.5 的 .git/hooks/post-checkout,执行 loopx --format json integration-branch sync --repo-path <fixture> --execute,保留默认 10 秒。不可变 base 约 13.57 秒成功、0 个失效 worktree;本 head 约 11.54 秒返回 rc=2/invalid_state 和 worktree add 超时,git worktree list --porcelain 多出 1 个 prunable gitdir file points to non-existent location 条目。移除 hook 后重试同步成功,该条目依然存在。周期同步每次遭遇同样超时都会累积本能力自己的失效登记,不能由“已退出”或“重试成功”证明资源已收回。

最小修复:让 _build_candidate 在 worktree add 已产生部分效果而抛错时,也撤销本次 candidate 登记;清理即使出错也要执行目录收尾并保留原超时。只处理本次拥有的路径,不全局 prune 其他工作。新增真实 Git 的慢 post-checkout 回归,断言超时后 worktree 清单、临时目录、integration branch 和 last_sync 均符合预期,再验证解除故障后的成功与重复执行。

PASS:77 tests,1 skipped;新 3 条预算测试和原 integration、共享 process runtime 测试均通过。独立 CLI 的 configure preview/execute/replay、drift status、sync preview/execute/replay、in-sync status 和越界 plan 拒绝,共 9 项的完整 JSON/退出码在 base/head 相同;上面的写中断反例 FAIL,现有 fake-git 测试没有覆盖登记后 hook 阻塞。

本地 standard premerge 的 6 项直接检查和 2 项 catalog 检查通过。另行执行的 maintainability ratchet 仍 FAIL:Lark private_conversations.py 的 module_metric_budget;同一命令在不可变 base 与 head 的完整 450 字节输出相同,相关规则、配置及模块未改,原阈值保留。它是独立告警,不能掩盖本次反例。未查询或等待远端 CI。

原生 Windows 未运行,不能把 POSIX 或模拟分支当作 Windows 资格。10 秒按每次调用重新分配,另有有界清理时间;不是整条 sync 的总 deadline。默认仍不刷新 remote、preview 不发布 integration ref,stdin 从继承改为 DEVNULL 的行为也应作为自动化非交互边界明确保留。

我的整体评价 / Verdict

long_horizon:regression,重复超时可累积失效 worktree 登记;user_experience:regression,返回错误和成功重试都没有揭示或清掉遗留资源。当前必要修复是既有 candidate 生命周期内的清理,不是强制改写到 TypeScript 或增加全局状态。此处未改 rendered presentation,已检查 CLI/plan/Git 生命周期,visual_review 为 not_applicable,未验证范围是原生 Windows、生产采用与长期完整成本。共享词汇、actor 与权限未扩张,架构和代码量基本适配;正确方向及绿色单测仍不能覆盖已复现的生命周期缺口。修复后需在新精确 head 重跑完整 PR 及上述真实反例。

English verdict: REQUEST_CHANGES — timed-out worktree creation leaves a stale registration after recovery.

try:
stdout, stderr = process.communicate(timeout=GIT_READ_TIMEOUT_SECONDS)
except subprocess.TimeoutExpired:
_retire_owned_read(process)

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.

[P2] 超时后仍需撤销已产生的 worktree 登记。真实 post-checkout hook 等待11.5秒时,worktree add 已在 common dir 登记,随后被默认10秒预算中断;_build_candidate 尚未执行 added=True,finally 跳过 worktree remove,却删除临时目录。本 head 返回 rc2 后遗留一个 prunable 登记,移除 hook 并成功重试后仍存在;相同 base 成功且没有遗留。请让既有 candidate owner 在 add 部分完成后失败时也清理本次登记,保持原超时并在清理失败时仍收尾目录,避免全局 prune 无关工作。请补真实 Git 回归覆盖超时后的清单、目录、branch/receipt 不变及恢复。

This branch has not been deployed

No deployments
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