fix(keeper): 评论任务对「已关闭 issue」永不收尸——3 条陈尸占满 comment_max_in_flight=3,全仓人类评论自动回评自 10-04 停摆

オープン
#19 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
58/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
github, python
領域
backend, devtools

調査の方向性

Start with issue_keeper/keeper.py, especially _count_comment_tasks, _collect_comment_tasks, _process_resource, and the round-level pipeline cleanup; then read issue_keeper/sources/github.py around the open-resource listing and the relevant tests. Reproduce the case with a processed item carrying comment tasks that is absent from list_open, and verify a run clears the orphaned tasks and still dispatches a new comment agent. Confirm cleanup is observable without changing the configured concurrency limit.

索引モデルが issue の本文から書いたものです。

説明

优先级 P1 · 依赖:无

基线 HEAD = 1b5dea0

Summary

「人类评论自动回评」自 2026-10-04 起全仓停摆:state 中 3 条评论异步任务陈尸(进程早死、所属 issue 已关闭)被 _count_comment_tasks 一直计入,占满全局闸 comment_max_in_flight=3,此后任何仓的任何新评论都不再派发评论 agent。停摆是静默的(无 ERROR、gate 提示是 INFO)。

证据 1 — 3 条陈尸记录(远端 ~/.issue-keeper/state.json 实读),三条均 attempts:1、pid 早死:

仓 & 单 comment id pid started_at(UTC) issue 关闭时刻(UTC)
jeffkit-browser-bridge #2 IC_kwDOUS-An88AAAABZFYsgg 29121 2026-10-04 09:10:24 09:14:33
jeffkit-ilink-hub #29 IC_kwDOSxmvrc8AAAABZF1GDg 11930 2026-10-04 11:07:53 11:17:09
jeffkit-ilink-hub #27 IC_kwDOSxmvrc8AAAABZF1EBw 12482 2026-10-04 11:08:05 11:32:10

三单 git/gh 实核均为 CLOSED;三个 pid 在 VM 上已不存在(ps -p 29121/11930/12482 全空;VM 于 10-05 23:35 重启,pid 空间早已重置)。共同特征:评论 agent 派发后 4~24 分钟内 issue 被关闭。

证据 2 — 计数不问死活也不问 open(issue_keeper/keeper.py:1576-1582):

def _count_comment_tasks(state) -> int:
    """全局在途评论任务数(跨仓;按任务记录计数——死任务由收尸清除)。"""
    n = 0
    for rs in state.repos.values():
        for it in rs.items.values():
            n += len(getattr(it, "comment_tasks", None) or {})
    return n

注释里「死任务由收尸清除」的前提在下面的证据 4 里不成立 → 记录永久留存 → 恒返 3。

证据 3 — 闸门与默认值(keeper.py:739-741 + config.py:277):

if _count_comment_tasks(state) >= max(1, config.comment_max_in_flight):
    log.info("[%s] 评论 agent 并发已达上限 (%d),本轮不派发新评论", ...)
    return handled

默认 comment_max_in_flight: int = 3;远端 ~/.issue-keeper/config.yaml 无该键(反证:grep -n comment_max_in_flight ~/.issue-keeper/config.yaml 零命中)⇒ 生效值恰为 3,与 3 条陈尸相等 ⇒ 容量恒为 0,不是"减少"而是"清零"。

证据 4 — 唯一收尸入口只服务于 open 资源:全仓 grep -n comment_tasks issue_keeper/keeper.py 显示 _collect_comment_tasks 只有一个调用点(keeper.py:682),位于 _process_resource() 内;而 _process_resource 只被 keeper.py:502 的 for res in resources: 调用,resources 来自 keeper.py:489 src.list_open(binding.repo, [kind], labels) —— GitHub 适配器里该调用固化为 --state open(issue_keeper/sources/github.py:170-176):

args = [kind, "list", "--repo", repo, "--state", "open", ...]

⇒ issue 一旦关闭就永远不再进入 _process_resource,其 comment_tasks 记录再也没有任何代码路径去收(items 记录本身留在 state 里,repos/items 不会被清理)。这不是超时逻辑失效,而是一条结构性的孤儿路径:收尸只发生在"资源被再次扫描到"时。

证据 5 — 日志量级(远端 ~/.issue-keeper/keeper.log,覆盖 2026-10-06 00:47 至今):

  • 评论 agent 并发已达上限:486 条(首条 2026-10-07 19:47:18 · recursive#134;末条 2026-10-08 17:59:38 · plaita#50)
  • 评论 agent 完成:1 条(2026-10-06 02:49 lavs#19)
  • 评论 agent 无输出,已自动重试:1 条(2026-10-06 02:43 lavs#19)

(日志起点晚于陈尸产生时间 10-04,故"停摆起点"由证据 1 的 started_at + 关闭时刻推定;日志只用于量级佐证。)

影响

谁踩:任何在 jeffkit/* 九仓里留评论的人(含人工协调线程的 okguitar/jeffkit)——评论进去以后永远不会被自动回评,且不会报错:state 健康、keeper 每轮"处理 0 条"正常退出、只有一条 INFO 级 gate 日志。停摆已持续 4 天。

代价:①人机协作闭环断在我方(评论→回评→验收→关闭)的第一步,值守/提报方只能靠人工发现"没人理我";②3 个全局槽位长期被无法自愈的僵尸占满,即使没有本次泄漏,任何一个"派发后立刻关闭"的 issue 都会永久吃掉 1 个槽位(最坏 3 次即全停)——这是一条可复现的慢性容量泄漏,本次只是刚好一次踩满;③comment agent 的派发/完成/重试三条日志在停摆期近乎零,掩盖了故障(观测面上"没有异常")。

注意:本条与已关闭的 #7(收尾只发生在轮首一次)不同——#7 是时序窗口问题,本条是闭合态资源不再被访问导致的永久孤儿,与轮次时机无关。

建议

任选其一或组合(建议 a 为主、b 为兜底):

(a) 收尸与 open 状态解耦:每轮开头(与 _reap_pipelines 同处)加一遍全量 items 扫,对不在 open 列表里的 item 收 comment_tasks(只收尸、不回评亦可:进程已死且 issue 已关闭,回评发在关闭单上无意义——但必须清记录)。

(b) 计数改为按存活计:_count_comment_tasks 只统计 _comment_proc_alive(pid) 为真的任务,或对超时/死进程记录在计数前先触发一次收尸;避免"状态账本"直接当"容量账本"。

(c) 关闭时收尾:keeper 观察到资源终态(关闭)时顺手收掉其 comment_tasks。

验收条件(可断言):

  1. 单测:构造一个 processed=True、带 comment_tasks 的 item,不出现在 list_open 返回集里;跑一轮后断言该 comment_tasks 已被清除(或 _count_comment_tasks(state) == 0)。
  2. 回归单测:state 中预置 3 条"死 pid + 已关闭 issue"的 comment_tasks,随后一条新评论到达 → 断言仍然派发评论 agent(而不是打"已达上限")。
  3. 现网自愈:部署后上述 3 条记录消失且闸门计数归零,任一仓新留人类评论能收到自动回评——判据=日志出现 评论 agent 完成 (comment=...) + 对应 issue 出现回评。
  4. 附带:新增一条 INFO/DEBUG 日志,在收尸时区分"回评成功/死进程丢弃/关闭丢弃",使该路径可观测(当前完全静默)。

边界

  • 不改 comment_max_in_flight 默认值与语义(3 的配额本身合理,坏的是账本口径)。
  • 不要求给已关闭 issue 补发 10-04 的历史回评(时效已过)。
  • 与 #2、#17、#3、#5、#11 等既有单无依赖关系,可独立修。

未证实附注(推测,非结论)

  • _comment_proc_alive(keeper.py:1562-1573)在 popen is None(跨重启接管路径)时退回 os.kill(pid, 0) 判活;pid 复用时会对一个陌生进程返回 True,随后 keeper.py:1645-1652 的超时分支会 os.killpg(pid, SIGKILL) 到不属于 keeper 的进程组。本次 3 条陈尸恰好走了"资源不再被访问"路径,未触发该分支,故未观察到实害;仅提示这条兜底判活值得一并加"pid 归属校验"(如比对启动时间 / 只信任 popen 句柄)。
主要言語
Python
スター
0
フォーク
0
PR マージ指標
30日以内にマージされた PR はありません

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

jeffkit/issue-keeper のほかの issue

jeffkit/issue-keeper の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。