Repository navigation
fix(lark): hold the collector's Git reads to one budget - #8154
Conversation
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.
评审提交:a8e264d7028e6861b8331c1fd9c6d7025021b9a4。两个 P2 问题需要修复,正常配置路径保持兼容。
- [P2] 超时返回前清理 Git 辅助进程:真实 fsmonitor hook 的子进程在 CLI 错误返回后仍继续写入,重复调用会累积后台工作。定位:event_collector.py:59。
- [P2] 首次隐私查询超时后停止后续查询:tracked 查询已经 unavailable,仍启动 ignored 查询并再等完整超时。定位:event_collector.py:95。
动机 / Motivation
配置 Lark 采集器的操作者通过 collector-plan 检查本地配置能否安全使用。
Git 检查卡住时原命令会一直等待;本改动希望有界拒绝,但真实 fsmonitor 辅助进程仍在错误返回后运行。
正常、已跟踪拒绝和非 Git 路径的完整 JSON 保持一致;首次失败后仍多等一次超时,辅助进程也未停止。
本次只评审采集器配置检查,不安装或运行采集服务,不发送消息,也不授予合并权限。
改动思路 / Approach
配置加载器继续从选定项目根目录解析私有路径,要求 Git 证明 ignored/untracked,再生成只读服务计划。新 helper 捕获 Git 启动错误和超时,把没有回答交给已有的 CLI 错误出口,不需要迁移配置或重写路由。
Git 超时属于 Lark provider IO 边界;现有隐私判断可以保留,但失败后的剩余查询和完整进程生命周期应在同一边界内收束。
当前范围是 collector 配置校验及其回归测试,不扩大到服务管理、全局调度或通用 subprocess 框架。
具体改动 / Changes
依据改动前规范 loopx/extensions/lark/docs/lark-event-inbox.md,不可变修订 faab5f5。Local-private configuration 已实现:正常私有配置通过,跟踪配置和跨项目路径拒绝;Host collector lifecycle 的非 Git 私有目录及只读计划边界保持。该规范没有单独规定五秒端到端 SLO,本评审不新增这项义务。
关键代码讲解
_run_git_probe(第 50 行)把直接 Git 调用统一为每次五秒 timeout,异常变成 None;它尚未拥有完整辅助进程树。_project_config_path(第 72 行)把 None 视为无法证明隐私,但在检查 tracked 结果前已经执行 ignored。plan_lark_event_collector(第 559 行)继续读取配置和构造计划,不执行服务安装、启动或消息发送。
完整审阅两文件 +145/-15。本轮 65 项原生 collector 测试 PASS;独立真实 CLI 的 healthy、tracked、non-Git 完整 JSON 与基线逐字相同。扩展启用后新建私有配置、同容器中的不合规配置、跨项目逃逸,以及修复临时 Git hook 后重试,共六个基线/新版实际入口观察均通过。扩展仅注册在隔离 runtime,计划的 provider 执行哨兵没有触发。
对主干的风险 / Risks
真实 Git fsmonitor 反例在临时仓库中启动心跳辅助进程。新版约 11.49 秒返回结构化拒绝时,四个辅助进程仍存活,返回后的心跳计数由 143 增至 150;直接 Git 已被杀不等于整个调用树停止。另一个独立的 Git 包装器子进程反例约 5.85 秒返回后同样继续运行。测试随后清理了自己拥有的进程组,未改变用户 Git 或服务。应让探针拥有并有界取消完整子进程树,等待清理完成后再返回;补充真实 hook 的进程和心跳停止断言,不能只断言 CLI 不再卡住。
连续阻塞两项隐私查询时,基线约 15.20 秒完成,新版约 10.97 秒拒绝。首次查询已确定无法证明隐私后,仍花第二个五秒。应立即检查首个 unavailable,并将同一剩余截止时间传给后续必要查询。这是避免已经无用的等待,也对应本 PR 的共用预算目标,不是凭空设立新的性能 SLO。复测应断言首次超时后下一查询未启动,并保留完整成功、拒绝和非 Git payload 对照。
必需 Ruff 范围、配置内 17 个 mypy 源、严格 TypeScript、全树语义扫描及六项 premerge 直接检查 PASS。更宽的 ruff check . 保留 FAIL:基线和两个提交的完整 119 项诊断相同,33 个原因文件的整文件摘要均未变。raw maintainability ratchet 的同一未改函数仍为 62 个 decision points 超过 60、85 statements;没有改阈值或删除断言。CQR cqr_eb0eededc6af9c01f087 为 fail,原生 premerge 是 quality_invalid_receipt。既有失败与本 PR 缺陷分开归因,但质量和合并 hold 均保留。初次自建探针的入口及扩展前置条件错误单独保留,未计为产品路径通过。
我的整体评价 / Verdict
当前提交请求修改。隐私判断及正常 CLI 行为保持兼容,但长期自动重试的后台工作成本、有界拒绝体验仍未满足本次修复目标。Python provider 的位置合理,同域简化应先落实在失败立即停止及完整子进程取消,不需要引入通用框架或语言迁移。本改动没有渲染页面,不要求截图。修复后应重新审阅完整新提交;当前评审和质量记录不授予服务执行或合并权限。
English verdict: REQUEST_CHANGES — supervise Git helper descendants and stop after the first unavailable ownership answer.
| """ | ||
|
|
||
| try: | ||
| return subprocess.run( |
There was a problem hiding this comment.
[P2] 超时返回前清理 Git 辅助进程
真实 Git core.fsmonitor hook 会启动辅助进程。当前 subprocess.run 超时只终止直接 Git;独立 collector-plan 反例在结构化拒绝返回后仍有四个辅助进程存活,心跳继续增长。反复检查会累积后台工作。请让探针拥有并有界取消、等待完整子进程树,并补充真实 hook 的返回后无进程/无心跳断言。
There was a problem hiding this comment.
已修,新 head 3f5211ab1。
探针现在拥有它启动的进程组
_run_git_probe 不再用 subprocess.run(timeout=...),改为以独立会话启动(POSIX 下 start_new_session=True)。预算用尽时由 _retire_owned_probe 调用既有的 terminate_process_tree 回收这一次调用自己的进程组,然后关闭 stdout/stderr 而不是排空:继承了管道的后代在 leader 死后仍然握着写端,排空会一直等一个已经没人负责的写入端。收尾最后是 wait(timeout=1.0)。
真实 fsmonitor 反例
一次性仓库里 core.fsmonitor 指向一个永不回答、每 100ms 追加一行的 hook,ls-files 会等它,hook 的子进程同组:
| 收尾方式 | 返回结果 | 返回后 0.6 秒内心跳 |
|---|---|---|
| 只结束 leader(改前) | 结构化拒绝 | 继续增长 |
| 收回本次调用的进程组(本 head) | 结构化拒绝 | 不增长 |
新增用例 test_a_timed_out_probe_takes_the_helpers_it_started 断言两点:结果是拒绝,且返回后心跳停止;断言前先要求心跳文件非空,避免 hook 根本没被调用时把空断言当通过。该用例按 os.name != "posix" skip。
| check=False, | ||
| ) | ||
| tracked = _run_git_probe(root, "ls-files", "--error-unmatch", "--", str(relative)) | ||
| ignored = _run_git_probe(root, "check-ignore", "--quiet", "--", str(relative)) |
There was a problem hiding this comment.
[P2] 首次隐私查询超时后停止后续查询
tracked 查询已超时返回 None 时,隐私证明已经失败,但这里仍无条件启动 check-ignore 并再次获得完整五秒。真实双阻塞 CLI 约十一秒才拒绝。请在首个 unavailable 后立即返回,并为必要查询传递同一剩余截止时间;补充首次超时后不再启动下一查询的回归,同时保留完整正常/拒绝/非 Git payload 对照。
There was a problem hiding this comment.
已修,新 head 3f5211ab1。
三个问题共用同一个截止时间
_project_config_path 现在先取一次 _git_deadline(),rev-parse、ls-files --error-unmatch、check-ignore 三次读取都拿同一个截止时间;_run_git_probe 用 deadline - time.monotonic() 作为本次 communicate 的预算,预算已经不为正时直接返回 None、根本不启动进程。
tracked 返回 unavailable 时立即抛出既有的拒绝,不再启动 check-ignore:准入需要「被跟踪且未被忽略」的证明,第一条就问不出来时,第二条不可能改变结论,只会再花一个完整预算。
对照
用只延迟 --error-unmatch 的包装 git(其余命令照常执行真实 Git),记录每次调用:
| 条件 | 改前 head | 本 head |
|---|---|---|
--error-unmatch 阻塞 |
跑 rev-parse、ls-files、check-ignore 三条 |
只跑前两条,立即拒绝 |
| 拒绝耗时 | 两个 5 秒窗口 | 一个 5 秒窗口 |
新增用例 test_the_next_question_is_not_asked_after_one_goes_unanswered 断言日志里有 rev-parse 和 --error-unmatch、但没有 check-ignore,同一用例在改前的 head 上失败(check-ignore 确实被启动)。完整成功、被跟踪拒绝、非 Git 三条正常路径的对照仍由 tests/extensions/test_lark_event_collector_routing.py 与 test_lark_event_collector_runtime.py 覆盖,合计 66 passed。
a8e264d to
e987193
Compare
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: model_agent; model=gpt-6.1-sol; provider=OpenAI; declaration_source=runtime_reported; reasoning_effort=max.
评审提交 e987193。REQUEST_CHANGES:超时修复成立,仍需修复取消后遗留 Git 进程的问题。
动机 / Motivation
配置 Lark 采集器的操作者通过 collector-plan 检查私有配置能否安全使用。
Git 所有权查询卡住时,旧实现会无限等待;本次改为共用截止时间拒绝,但用户取消后仍遗留辅助进程。
正常配置、已跟踪拒绝、非 Git、扩展停用及路径逃逸保持原有结果;超时会拒绝并清理,Ctrl-C 清理仍失败。
本次只评审配置准入,不安装或运行采集服务、不发送消息,也不授予合并权限。
改动思路 / Approach
实际入口是 loopx lark-inbox collector-plan,沿现有配置/扫描 owner 读取文件与真实 Git 索引,再将结果交给现有 CLI JSON 和错误出口。没有理由继续保留无上限等待;只加 subprocess timeout 也不足以管理 Git 启动的 fsmonitor 辅助进程。当前采用独立进程组并复用 OS 清理能力,方向合理。
Git IO 保留在现有 Python owner,OS 取消复用 process_runtime;安全判断不交给传输层,也不建立第二份状态。
当前 PR 只需补齐同一探针的异常退出清理;无需创建通用框架或进行语言迁移。
与 registry 的有界 Git 探测及相关 #8152/#8153/#8154 比较后,没有要求跨模块调用私有 helper 或新增领域规则。用表格区分正常超时与取消,比另画流程图更直接:
| 实际入口观察 | 当前结果 |
|---|---|
| 正常/合法与不合规输入 | 安全结果保持 |
| Git 预算用尽 | 明确失败并清理组 |
| 用户 Ctrl-C | CLI 退出,隔离 Git 组继续工作 |
具体改动 / Changes
规范 Local-private configuration / Host collector lifecycle,不可变修订 927d0b3。Local-private configuration 已实现:配置仍为本地私有;Host collector lifecycle 的 ignored/untracked、非 Git 私有根与只读 service preview 边界已实现。规范未定义五秒端到端 SLO;本评审也不增加这项条件。
关键代码讲解
_run_git_probe,L81:Starts one owned Git group and uses remaining budget; timeout cleans, KeyboardInterrupt currently escapes.
_project_config_path,L123:Enforces project containment, Git ignored/untracked proof and non-Git private-root fallback.
plan_lark_event_collector,L613:Loads validated config and returns an unchanged redacted plan without service execution.
完整审阅两文件 +253/-13,生产代码 +93/-13,新增测试 160 行。当前四个原生模块 73 PASS,包含新增预算测试、routing、runtime 与 timing;作者提到的旧 timing 错误没有在本轮出现,不能把旧基线状态带到新提交。真实 CLI 的成功、跟踪拒绝、非 Git、扩展停用和逃逸拒绝均有对照。成功计划的 service_digest 不同来自隔离 runtime 路径;另从实际 service bytes 重算了摘要,归一化该已知路径后内容相同,没有直接删除摘要来宣称逐字相等。
首次跟踪查询延迟时,新版约 5.57 秒拒绝并不再启动 check-ignore;真实 fsmonitor 超时约 5.51 秒拒绝,返回后心跳 89→89,组内无存活成员。基线无预算的 hook 运行由测试的 18 秒安全上限终止,该上限不是产品性能成绩。
对主干的风险 / Risks
[P2] 在 Ctrl-C 退出前回收隔离的 Git 进程组(loopx/extensions/lark/event_collector.py:L115)。新增 start_new_session=True 后,终端向 CLI 进程组发送 SIGINT 不再覆盖 Git 组。这里仅在 TimeoutExpired 时调用清理;communicate 抛 KeyboardInterrupt 时直接退出。真实 loopx lark-inbox collector-plan + Git core.fsmonitor 对照中,基线取消后无存活进程,当前 head 留下 Git、hook 和辅助子进程,心跳在 CLI 退出后 400ms 从 8 增至 16。取消后的重试会累积后台工作。请在异常退出(至少 KeyboardInterrupt)时有界回收原组、释放管道并等待,再传播取消;保留超时拒绝语义。补真实入口的 SIGINT、后代停止及正常路径对照。
这是实际源码 CLI、真实 Git 和 fsmonitor 的 POSIX 对照,不是 mock 给出的后置条件。最初 shell 后台子进程继承忽略 SIGINT,基线也会遗留该子进程;这组结果保留。随后显式恢复辅助子进程的正常 SIGINT 行为,在相同 fixture 对照:基线两条入口均无存活成员、心跳 1→1,新 head 则各有 3 个存活成员。所有测试结束都只清理自己记录的进程组,并验证没有活成员。不能把测试兜底清理写成产品已正确取消。
CI 范围 Ruff、变更文件 Ruff、配置内 17 个 mypy 源、全树语义扫描、diff 和公开边界扫描 PASS。
语义与 CI 对齐
本次复用现有私有边界和局部诊断词汇,全树语义检查通过。TypeScript 必检仍 FAIL:基线与两个 head 的完整 158 字节诊断相同,conversation_binding.ts:1144 为 TS2345 unknown→string;对应 TypeScript、配置和锁文件整文件不变。raw maintainability ratchet 的完整 399 字节失败同样一致,定位到未变的 _admitted_interaction_next_cli_actions。不放宽预算、不要求本 PR 顺带修无关路径;严格 CQR 与 premerge 的原始 hold 单独保留。没有获取、轮询或等待远端 CI。
严格 CQR cqr_f4fa3f79c8896f7f858c 读回为 invalid_receipt。quick premerge 六项直接检查通过,语义检查通过,ratchet 在该执行器中为 advisory_inherited_failure;独立 raw 运行仍是 FAIL。整体 quality_invalid_receipt,未达到合并资格。
我的整体评价 / Verdict
完整当前提交请求修改。long_horizon=regression:取消后重试会积累后台工作;user_experience=regression:用户已经取消但实际 Git 工作仍在继续。正常和超时修复是可复核的有用改进,仍不能抵消这个新回归。代码范围与既有 Python IO owner 相称;同域简化应落实为一个异常安全的探针生命周期,保留现有输入与诊断。修复后需重新审查完整新 head,而不只复验这一行。
检查了实际 CLI 到现有 owner 的路径;没有渲染页面或布局变更,无需截图。
未验证原生 Windows 进程树及已安装 Host/采集服务行为。
这里的评审、CQR 与 fixture 记录均不授予安装、发布或合并权限,也不证明长期性能或模型采用收益。
English verdict: REQUEST_CHANGES — e987193: retire the isolated Git group on terminal cancellation; real timeout and healthy controls pass, while Ctrl-C leaves three live processes. Separate local validation holds remain.
| return None | ||
| try: | ||
| stdout, _stderr = process.communicate(timeout=budget) | ||
| except subprocess.TimeoutExpired: |
There was a problem hiding this comment.
[P2] 在 Ctrl-C 退出前回收隔离的 Git 进程组
新增 start_new_session=True 后,终端向 CLI 进程组发送 SIGINT 不再覆盖 Git 组。这里仅在 TimeoutExpired 时调用清理;communicate 抛 KeyboardInterrupt 时直接退出。真实 loopx lark-inbox collector-plan + Git core.fsmonitor 对照中,基线取消后无存活进程,当前 head 留下 Git、hook 和辅助子进程,心跳在 CLI 退出后 400ms 从 8 增至 16。取消后的重试会累积后台工作。请在异常退出(至少 KeyboardInterrupt)时有界回收原组、释放管道并等待,再传播取消;保留超时拒绝语义。补真实入口的 SIGINT、后代停止及正常路径对照。
There was a problem hiding this comment.
已修,新 head 9e4215e47。
取消时由等待的人回收它启动的会话
_run_git_probe 现在在 communicate 上区分两种「没等到答案」:
TimeoutExpired:回收本次调用自己的会话,返回None,配置照旧被拒绝。- 其他中断(终端 Ctrl-C 的
KeyboardInterrupt、SystemExit等):同样走_retire_owned_probe回收同一个会话,然后raise原样抛出。
独立会话让预算能收回 Git 的后代,代价就是这个会话收不到终端信号——那条路径上唯一的 owner 是正在 communicate 的这次调用,所以退出前必须在同一个所有权范围内收尾。超时拒绝语义没有变,Ctrl-C 仍是 Ctrl-C。
取消回归
新增 test_a_cancelled_command_takes_the_git_it_started:在独立进程里跑 collector-plan 分派的 plan_lark_event_collector 入口,PATH 前面的 git 把除 ls-files 外的命令都交给真实 Git,ls-files 则阻塞并由子进程持续写心跳(即真实 fsmonitor hook 被等待时的形状)。等心跳出现、确认命令仍在等待后,向该进程所在进程组发 SIGINT,也就是终端 Ctrl-C 的作用范围。
| 条件 | 改前 head e987193a0 |
本 head |
|---|---|---|
| 等待所有权查询时收到组 SIGINT | 命令以非零退出,Git 与子进程仍存活,退出后心跳 0 字节 → 45 字节 | 命令以非零退出,心跳停止 |
断言覆盖三点:取消后命令非零退出、心跳在返回后 0.6 秒内不再增长、以及 fixture 确实阻塞过(心跳先出现,避免空断言)。每次运行都会写 stop 文件并回收测试自己创建的进程组;用例按 os.name != "posix" skip。同一用例在改前 head 上失败。
超时分支的真实 core.fsmonitor 用例保留,正常/被跟踪拒绝/非 Git 三条路径对照不变;三个文件合计 67 passed,ruff --select E4,E7,E9,F 全绿。
Admitting a collector config path asks Git three questions -- whether the project is a worktree, whether the file is tracked, and whether it is ignored -- and none of them carried a budget. A Git blocked on a stale index lock, a network mount or an oversized index therefore held the command open with no answer at all; reproduced here with a fake `git` on PATH that only sleeps, where `_project_config_path` was still running after 5s. The budget alone was not enough: a `TimeoutExpired` would have escaped the admission check as a traceback, so the refusal paths had to say what "Git did not answer" means. Acceptance needs proof that the config is ignored and untracked, so an unanswered probe refuses the path. The three reads share `GIT_PROBE_TIMEOUT_SECONDS` through `_run_git_probe`, and a Git that exits non-zero still takes the existing boundary and non-Git fallbacks unchanged. The queries are local reads that never fan out into helpers, so the leader-only kill that `subprocess.run` performs covers them. Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
Two gaps in the config admission. The budget ended only the Git leader. Git starts helpers of its own -- an fsmonitor hook is the common one on `ls-files` -- inside the group it runs in, so a blocked admission returned its refusal while the hook and its child kept running, and repeated runs accumulated them. The probe now runs in a session of its own, and an exhausted budget retires that group through `terminate_process_tree` before dropping the pipes instead of draining them. The three questions each carried a fresh budget. Once the tracked query had timed out the admission could no longer be satisfied, yet `check-ignore` was still started and spent another full budget on the same hang. The questions now share one deadline, an unanswered tracked query refuses at once, and a question asked after the deadline has passed is not started at all. Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
The budget covered an exhausted wait, not an interrupted one. The probe runs in a session of its own so an exhausted budget can retire the helpers Git started, which also means a terminal Ctrl-C -- a SIGINT to the process group of the running `collector-plan` -- never reaches Git. `communicate` raised `KeyboardInterrupt` out of the only place that owns that session, the command exited, and the blocked Git and its helper kept running. Repeated cancellations accumulate the same background work the budget fix removed. The wait now retires the owned session on any interrupted exit and re-raises the original interruption: an exhausted budget keeps refusing the config, while Ctrl-C stays Ctrl-C. Reproduced with the `collector-plan` planning entry in a real child process, real Git and a `git` that blocks the ownership read with a helper writing heartbeats: interrupting the process group left the heartbeat growing on the previous head and stops it now. Signed-off-by: ChengLong GU <88359731+gcl-coder@users.noreply.github.com>
e987193 to
9e4215e
Compare
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
动机 / Motivation
配置 Lark 采集器的操作者通过 collector-plan 检查私有配置能否安全使用。
旧实现遇到不回答的 Git 会一直等待;中间版本取消后仍有辅助进程写心跳,本 head 会在拒绝或取消前清理自己启动的会话。
真实 CLI 的超时与取消已完成清理,恢复 Git 后原命令可继续产出同一只读计划。
本次不安装或运行采集服务,不发送消息,也不认证在线 provider 或实际 Host 采用。
改动思路 / Approach
预算与取消清理留在 Lark 的配置准入 owner,OS 回收复用既有 process_runtime,职责和规模合适。 当前范围是配置准入与只读计划;服务安装、实际采集和消息发送保持原边界。
一次配置证明共用一个单调时钟截止时间,根查询、跟踪和忽略查询只使用剩余预算;已经没有答案时继续询问不能改变拒绝结论。Git 独立会话使超时能够回收后代,也使终端信号只到达 CLI,因此等待者必须在取消传播前收尾。仅杀直接 Git 或仅断言父命令退出都不足够。
比较了 registry/doctor 的相邻 Git owner:它们的诊断结果、环境处理和预算不同。这里复用 OS 回收而保留配置拒绝规则,比跨域调用私有 helper 或新增通用框架更合适。
具体改动 / Changes
重新审阅完整 base 7bdae359e66c007eddbf7672578016298c8ac8e4 → head 9e4215e47e72865fe612c8db91a1089c96321c9d,两文件 +347/−13;生产 +102/−13,新增测试245行。没有继承上次结论。相对上次受审 e987193a0cd3617e4b8d0bf4439a22c8318e21de,取消路径新增收尾后原样抛出;同时重新验证整个当前提案的正常、超时、作用范围和恢复。
先读的规范是 loopx/extensions/lark/docs/lark-event-inbox.md,固定修订 7bdae359e66c007eddbf7672578016298c8ac8e4。Local-private configuration 已覆盖:扩展停用时不读取配置、不执行 provider;Host collector lifecycle 已覆盖:Git 配置必须 ignored/untracked,非 Git 仅接受私有根,越界拒绝,安装仍需单独 execute。规范没有五秒端到端 SLO,本评审也不增加这个要求;观察值包含 CLI 与清理开销。
关键代码讲解
_run_git_probe使用剩余预算启动和等待;超时返回 None,其他中断回收后重新抛出,不吞掉取消。_retire_owned_probe复用进程树终止器,关闭继承管道而不等待 EOF,再有界等待 leader;作用范围是本次拥有的会话。_project_config_path保留实际路径与隐私证明,只生成一次 deadline,并在首次 unavailable 时停止后续查询。plan_lark_event_collector是实际 CLI 调用的原 owner,返回同一脱敏计划;其他 loader 消费者及权限门没有另建规则。
对主干的风险 / Risks
未发现阻断当前 head 的 finding。此前两个超时问题和取消问题均以独立实际入口重新核验:
| 真实 CLI / Git 条件 | 当前观察 |
|---|---|
| 跟踪查询延迟 | 约5.583秒拒绝,未启动 check-ignore |
| 根查询与跟踪查询合计超过预算 | 约5.620秒拒绝,共用一个窗口 |
| fsmonitor hook 与真实子进程不回答 | 约5.539秒拒绝,0存活成员,返回后心跳88→88 |
| 终端组 SIGINT | 约1.098秒非零退出,0存活成员,心跳5→5 |
| 上次 e987 的同一取消反例 | 仍有3个存活成员,心跳8→15;本轮兜底回收另记 |
共25次实际 CLI 观察。成功、被跟踪拒绝、非 Git、扩展停用、路径逃逸、恢复、启用后新配置及 symlink 逃逸的八组 base/head 完整 JSON 与原始 stdout 摘要一致;没有删掉 service_digest 来凑等价。停用分支没有 Git 查询,provider/服务执行哨兵没有触发。原始无预算 base 的挂起由18秒测试安全上限终止,这不是产品返回成绩。隔离测试 runtime 首次复用触发 already-installed,保留该失败后归档自己的 fixture runtime,按每个源码版本重新安装;未修改全局安装或产品断言。
PASS:当前五个完整模块132项、base共同模块126项,均有1项 Windows junction skip;CI声明 Ruff 范围、变更 Python Ruff/编译、配置内17根 mypy、严格 control-plane TypeScript、全 semantic drift/advisory、diff、公私扫描和三次提交 sign-off。旧评审中的 TypeScript 失败没有在当前 base/head 出现,未带入本头。
语义与 CI 对齐
复用既有词汇与配置 owner,只增加局部 deadline/结果处理。raw maintainability 仍 FAIL:未改的 _admitted_interaction_next_cli_actions 为62 decision points,阈值60,85 statements。完整结构化报告除仓库根地址外逐项相同,失败来源、checker、阈值、扫描集合和环境均核对;必要原生测试与真实 CLI 独立覆盖全部变更路径。它按原生 exact-scope optional baseline-attribution 契约资格化,仍保留 failed/risk,并没有改阈值或把失败改成 PASS。严格 CQR cqr_51d1fb96c9d3182ddc6d verify 有效;最终 quick premerge 六项直接检查及两项选中检查通过,保留一项 inherited advisory。初次 receipt 字段长度拒绝与 missing-receipt 门禁均保留,修正的只是本评审数据表达。远端 CI 未获取、轮询或等待。
我的整体评价 / Verdict
批准当前完整 head。long_horizon 的改进限于避免已复现的反复后台工作与恢复阻塞;user_experience 改善为有界拒绝、取消确实停止以及原命令可恢复,没有新增准备或确认轮次。配置规则、必要证明与已有正常计划保持。
同域简化已经体现在一个 deadline 和首次失败即停止;私有 helper 的必填 deadline、公共 pipe-release adapter 可在下一次必要触及时考虑,当前没有需要追加框架或新任务的证据。本次只改 Python 配置准入与测试,没有渲染页面。原生 Windows、实际安装服务/Host 与在线 provider 未验证。源码、打包/安装、实际采用及完整成本收益保持区分;旧评审线程、即时 readiness 与合并授权另行判断。
English verdict: APPROVE 9e4215e — shared-budget refusal and owned Git cleanup now pass real timeout, cancellation and recovery checks; unrelated baseline debt remains explicitly failed.
Goal And Delivered Outcome
Outcome basis / optional anchor: 无独立 issue;本 PR 给 Lark collector 配置准入的 Git 读取加预算,并按评审意见补上进程组回收与「第一条问不出来就停」。
Goal/source and gap:
_project_config_path为了判断一份 collector 配置能否被接受,连跑三次 Git(rev-parse --show-toplevel、ls-files --error-unmatch、check-ignore --quiet)。三次都没有预算时,git 卡住命令也就不返回;加上预算后又暴露三处:subprocess.run(timeout=...)只结束 Git 主进程,ls-files启动的core.fsmonitorhook 与其子进程留了下来;三个问题各自带一个完整预算,第一条已经问不出来时仍会再花一个预算启动check-ignore;其三,预算只覆盖「等到超时」,操作者按 Ctrl-C 时communicate抛KeyboardInterrupt,而 Git 因独立会话收不到终端信号,退出路径上没有任何东西回收它启动的 hook 与子进程。Observable before → after, with the validation row that proves it:
core.fsmonitorhook 不回答--error-unmatchcheck-ignore,再等一个完整预算sleep 30的假 gitmain.Author Declaration
Implemented against
loopx/extensions/lark/docs/lark-event-inbox.md的 Local-private configuration 一节;loopx/registry.py在 fix(registry): retire the git probe process tree on timeout #8115 中确立的进程组回收方式是实现依据。_project_config_pathtests/extensions/test_lark_event_collector_routing.py_git_deadline、_run_git_probe(deadline=...)pytest -q tests/test_lark_event_collector_git_budget.py_project_config_path的tracked is None分支_retire_owned_probecore.fsmonitor反例_retire_owned_probe关闭管道后wait(timeout=1.0)_run_git_probe的except BaseException分支test_a_cancelled_command_takes_the_git_it_startedrev-parse非零时的边界回退test_lark_event_collector_runtime.py_project_config_path的两个调用点,确认返回值只有「接受路径」与「拒绝」两种;用延迟查询的包装 git 记录每次调用,确认改前会启动check-ignore、改后不会;用真实 fsmonitor hook 验证超时后后代停止;取消路径用独立进程跑collector-plan的规划入口、真实 Git 与阻塞的所有权查询复现,改前 head 在命令退出后心跳仍从 0 涨到 45 字节,本 head 停止,同一用例在改前 head 上失败。新增 6 条用例,与 routing/runtime 两个文件合计67 passed;三个 lark 相关架构用例此前114 passed;ruff --select E4,E7,E9,F全绿。tests/extensions/test_lark_event_timing.py的 6 条 error(ChatHTTPServer缺background_services)在未改动的基线上同样出现,与本改动无关。没有在原生 Windows 上执行进程树回收,fsmonitor 用例按os.name != "posix"skip。Scope And Continuation
只改
_project_config_path与其私有 helper,外加一个新测试文件。loopx/contract.py与loopx/doctor_git.py的同族问题分别在 #8152、#8153 处理。