Skip to content

feat(multi-review): single comment output + remove standalone reviews#98

Closed
Svtter wants to merge 8 commits into
mainfrom
feat/multi-review-cleanup
Closed

feat(multi-review): single comment output + remove standalone reviews#98
Svtter wants to merge 8 commits into
mainfrom
feat/multi-review-cleanup

Conversation

@Svtter
Copy link
Copy Markdown
Collaborator

@Svtter Svtter commented May 21, 2026

Summary

  • Add _filter_noise() to strip CLI boilerplate from review output
  • Add cleanup_reviewer_comments() to delete per-reviewer comments, keeping only coordinator synthesis
  • post_pr_comment() returns comment ID via gh api for precise cleanup targeting
  • Keep USE_GITHUB_TOKEN=true to avoid OIDC crash; cleanup is post-hoc
  • Remove standalone review.yml and architect-review.yml — multi-review covers these

Test plan

  • python3 -m pytest tests/test_multi_review.py -v — 21 tests pass
  • CI passes; PR only has multi-review comment (no duplicate review/architect-review comments)

🤖 Generated with Claude Code

Svtter and others added 4 commits May 21, 2026 16:55
…ering

- Add _filter_noise() to strip CLI boilerplate (tool calls, log lines,
  session metadata) from reviewer output
- Add cleanup_reviewer_comments() to delete per-reviewer comments after
  coordinator synthesis is posted, keeping only the final comment
- post_pr_comment() now returns comment ID for precise cleanup targeting
- Keep USE_GITHUB_TOKEN=true to avoid OIDC crash in opencode CLI

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Use `gh api --input` instead of `gh pr comment` so we get the comment
ID in the response. Pass this ID to cleanup_reviewer_comments() for
precise targeting — avoids relying on comment ordering which is not
guaranteed with concurrent reviewers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
gh api --input expects a file path, not inline data. Use stdin pipe
(--input -) with subprocess.run(input=...) instead.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
multi-review already covers these use cases. Keeping separate workflows
causes duplicate comments on every PR.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

无遗漏

PR 标题和 body 列出的 5 项改动均已完整实现:

  1. _filter_noise() — 定义于 multi-review/run-multi-review.py:425,在 format_pr_comment()(第 453、456 行)和 post_fallback_comment()(第 467 行)中均有调用
  2. cleanup_reviewer_comments() — 定义于第 567 行,在 coordinator 超时回退路径(第 777 行)和正常发布 synthesis 后(第 803 行)均有调用
  3. post_pr_comment() 返回 comment ID — 第 507 行返回 comment_id,用于 cleanup_reviewer_comments 的精确定位
  4. 保留 USE_GITHUB_TOKEN=true — 第 201 行和 env_forward(第 660 行)均已设置
  5. 删除 standalone workflow.github/workflows/review.ymlarchitect-review.yml 均已删除

所有新增的函数都在主流程中被正确调用,cleanup_reviewer_comments 覆盖了 coordinator 成功和失败两条路径,Comment ID 传递链路完整。

New%20session%20-%202026-05-21T09%3A18%3A15.773Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

Asserting permissions for user Svtter...
permission: admin
Adding reaction...
Fetching prompt data for PR...
Checking out local branch...
Sending message to opencode...
service: "session.prompt",
"session.id": "ses_1b629ba5effebpZNRB2dqCpYBH",
step: 0,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
service: "session.prompt",
"session.id": "ses_1b629ba5effebpZNRB2dqCpYBH",
step: 1,
}
service: "session.prompt",
"session.id": "ses_1b629ba5effebpZNRB2dqCpYBH",
}
Checking if branch is dirty...
Creating comment...
Removing reaction...


详细审查报告:

✅ security

Asserting permissions for user Svtter...
permission: admin
Adding reaction...
Fetching prompt data for PR...
Checking out local branch...
Sending message to opencode...
service: "session.prompt",
"session.id": "ses_1b62be0a1ffeiA7KBbIUAjIgWR",
step: 0,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

service: "session.prompt",
"session.id": "ses_1b62be0a1ffeiA7KBbIUAjIgWR",
step: 1,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
service: "session.prompt",
"session.id": "ses_1b62be0a1ffeiA7KBbIUAjIgWR",
step: 2,
}
service: "session.prompt",
"session.id": "ses_1b62be0a1ffeiA7KBbIUAjIgWR",
}
Checking if branch is dirty...
Creating comment...
Removing reaction...

✅ quality

Asserting permissions for user Svtter...
permission: admin
Adding reaction...
Fetching prompt data for PR...
Checking out local branch...
Sending message to opencode...
service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
step: 0,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
step: 1,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
step: 2,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
step: 3,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
step: 4,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
step: 5,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
step: 6,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
step: 7,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
step: 8,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
step: 9,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
step: 10,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
step: 11,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
step: 12,
}
service: "session.prompt",
"session.id": "ses_1b62be052ffeTto4PvNTdUvqXg",
}
Checking if branch is dirty...
Creating comment...
Removing reaction...

…skipping

The noise prefix matching was case-sensitive but actual CLI output uses
title case (e.g. "Asserting permissions"). Also add continuation line
skipping: after a noise header, skip indented JSON-like fragments
(session.id, llm.runtime, etc.) until the next blank line.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

无遗漏

PR 标题和 body 列出的 5 项改动均已完整实现:

  1. _filter_noise() — 定义于 run-multi-review.py:441,在 format_pr_comment()(第 481、484 行)和 post_fallback_comment()(第 495 行)中均有调用
  2. cleanup_reviewer_comments() — 定义于第 595 行,在 coordinator 超时回退路径(第 805 行)和正常发布 synthesis 后(第 831 行)均有调用
  3. post_pr_comment() 返回 comment ID — 第 510 行返回 int | None,返回值在第 824 行被捕获并传入 cleanup_reviewer_comments
  4. 保留 USE_GITHUB_TOKEN=true — 第 201 行 _run_opencode() 中和第 688 行 env_forward 表中均已设置
  5. 删除 standalone workflow — commit 6dd8b1a 确认删除了 .github/workflows/review.ymlarchitect-review.yml(-67 行)

所有新增函数在主流程中均被正确调用,Comment ID 传递链路完整,两条路径(coordinator 成功/失败)均调用了 cleanup_reviewer_comments,无需额外修复。

New%20session%20-%202026-05-21T09%3A47%3A50.669Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

[09:48:39.407] INFO (#277): loop {
service: "session.prompt",
"session.id": "ses_1b6101e30ffeUdj3je6ezTMg6f",
step: 0,
}
[09:48:39.452] INFO (#291): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
[09:48:39.567] INFO (#277): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
[09:48:45.584] INFO (#277): loop {
service: "session.prompt",
"session.id": "ses_1b6101e30ffeUdj3je6ezTMg6f",
step: 1,
}
[09:48:45.586] INFO (#277): exiting loop {
service: "session.prompt",
"session.id": "ses_1b6101e30ffeUdj3je6ezTMg6f",
}


详细审查报告:

⚠️ quality
✅ security

[09:47:51.858] INFO (#284): loop {
service: "session.prompt",
"session.id": "ses_1b610d76bffe2i30yjYzSt03ZX",
step: 0,
}
[09:47:51.902] INFO (#299): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
[09:47:52.096] INFO (#284): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

[09:47:54.690] INFO (#284): loop {
service: "session.prompt",
"session.id": "ses_1b610d76bffe2i30yjYzSt03ZX",
step: 1,
}
[09:47:54.719] INFO (#284): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

[09:47:57.115] INFO (#284): loop {
service: "session.prompt",
"session.id": "ses_1b610d76bffe2i30yjYzSt03ZX",
step: 2,
}
[09:47:57.137] INFO (#284): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

[09:47:59.597] INFO (#284): loop {
service: "session.prompt",
"session.id": "ses_1b610d76bffe2i30yjYzSt03ZX",
step: 3,
}
[09:47:59.617] INFO (#284): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

[09:48:01.580] INFO (#284): loop {
service: "session.prompt",
"session.id": "ses_1b610d76bffe2i30yjYzSt03ZX",
step: 4,
}
[09:48:01.603] INFO (#284): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
[09:48:35.334] INFO (#284): loop {
service: "session.prompt",
"session.id": "ses_1b610d76bffe2i30yjYzSt03ZX",
step: 5,
}
[09:48:35.337] INFO (#284): exiting loop {
service: "session.prompt",
"session.id": "ses_1b610d76bffe2i30yjYzSt03ZX",
}

opencode CLI's stdout is just boilerplate (permissions, session info,
tool calls). The actual AI review content is posted as a PR comment by
opencode CLI itself. After each reviewer finishes, fetch that comment
and use it as the real review content for coordinator synthesis and
display in collapsible details.

- Add _fetch_latest_bot_comment() to get the latest github-actions[bot]
  comment from the PR
- Store fetched comment in reviewer_result["comment"]
- Coordinator, format_pr_comment, and post_fallback_comment all prefer
  "comment" over "output"

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

[09:52:51.285] INFO (#276): loop {
service: "session.prompt",
"session.id": "ses_1b60c4584ffefo61TmFVPZFBuW",
step: 0,
}
[09:52:51.344] INFO (#290): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
[09:52:51.498] INFO (#276): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
[09:53:09.371] INFO (#276): loop {
service: "session.prompt",
"session.id": "ses_1b60c4584ffefo61TmFVPZFBuW",
step: 1,
}
[09:53:09.375] INFO (#276): exiting loop {
service: "session.prompt",
"session.id": "ses_1b60c4584ffefo61TmFVPZFBuW",
}


详细审查报告:

✅ security

分析完毕

存在风险

本次 PR 为功能增强,subprocess.run() 全部使用 list 参数(无 shell=True),无 eval/exec,YAML 使用 safe_load,没有引入 SQL 注入、命令注入或密钥硬编码。但存在以下问题:

阻塞项:无

建议项:

  1. MULTI_REVIEW_EXTRA_ENV 任意环境变量注入run-multi-review.py:789-795)— 允许通过 env 设置任意环境变量(如 PATHLD_PRELOAD),虽在 CI 沙箱内风险受控,但若 CI secret 泄露则可能被利用实现提权。建议对 key 做白名单校验或添加前缀约束。
  2. reviewer 名称未做 HTML 转义run-multi-review.py:490)— r['name'] 直接嵌入 <summary> 标签,若 reviewer 配置中包含恶意字符可能造成 HTML 注入。虽 GitHub 会清洗 PR 评论中的 HTML,但建议使用 html.escape()
  3. Comment 删除逻辑可能误删run-multi-review.py:682-685)— keep_comment_idNone 时会删除除最后一条外的所有同 run 评论,若因时序问题导致 coordinator 评论未被正确识别,可能删错。建议优先依赖返回的 comment_id
  4. _fetch_latest_bot_comment() 竞态run-multi-review.py:514-550)— 并行 reviewer 同时获取最新 bot comment 可能读到彼此的评论。当前生产路径已传 before_comment_id 缓解,但回退路径(_run_reviewer_inner:312)未传此参数。
✅ quality

有条件合并

PR 整体改动方向合理:引入 _filter_noise 清理 CLI 噪音、cleanup_reviewer_comments 删除单个 reviewer 评论、让 post_pr_comment 返回 comment ID 用于精确定位,以及删除废弃的 standalone workflow。但有一个影响正确性的阻塞问题。

阻塞项

_fetch_latest_bot_comment()reversed() 导致返回的是最旧而非最新的 bot 评论(第 542 行)

GitHub API 返回 sort=created&direction=desc,评论列表已经是"最新在前";reversed() 后变为"最旧在前",循环第一个匹配就 return,实际返回的是符合条件的最旧 bot 评论,而不是最新的(最新评论在 reversed 列表的末尾,永远被忽略)。

影响:_run_reviewer_inner(第 312 行)调用 _fetch_latest_bot_comment() 获取刚发布的 reviewer 评论,如果之前已有更旧的 bot 评论(如重试场景),会取到旧评论而非刚发布的内容,导致 coordinator 拿到错误的 review 文本。

修复:移除第 542 行的 reversed(),直接迭代 comments

建议项

  1. _filter_noise() 中死代码(第 469 行 if stripped in ("}", "{"):)—— _NOISE_BLOCK_RE 的正则 ^\s*[}{]$ 已经匹配了 }{,显式 check 永远不会被触发。

  2. except Exception: pass 吞掉异常(第 634、696-697 行)—— 清理路径中静默吞异常虽常见,但建议至少 print(..., file=sys.stderr) 或使用 logging.debug 避免排查问题时缺少线索。

  3. _run_opencode_raw()_run_opencode_in_dir() 高度重复(第 223/241 行)—— 仅 cwd 参数不同,可合并为带 cwd=None 参数的单一函数减少重复。

@github-actions
Copy link
Copy Markdown

无遗漏

PR 标题和 body 列出的 5 项改动均已完整实现:

  1. _filter_noise() — 定义于 run-multi-review.py:425,在 format_pr_comment()(第 453、456 行)和 post_fallback_comment()(第 467 行)中均有调用
  2. cleanup_reviewer_comments() — 定义于第 567 行,在 coordinator 超时回退路径(第 777 行)和正常发布 synthesis 后(第 803 行)均有调用
  3. post_pr_comment() 返回 comment ID — 第 507 行返回 int | None,返回值在第 824 行被捕获并传入 cleanup_reviewer_comments
  4. 保留 USE_GITHUB_TOKEN=true — 第 201 行 _run_opencode() 中和第 731 行 env_forward 表中均已设置
  5. 删除 standalone workflow.github/workflows/review.ymlarchitect-review.yml 均已删除(-67 行)

所有新增函数在主流程中均被正确调用,Comment ID 传递链路完整,两条路径(coordinator 成功/失败)均调用了 cleanup_reviewer_comments,无需额外修复。

New%20session%20-%202026-05-21T09%3A51%3A30.457Z
opencode session  |  github run

- Log lines ([HH:MM:SS] INFO...) and tool call lines (| Shell...)
  now set skipping=True so their JSON-like continuation lines are
  also filtered out
- Fix _fetch_latest_bot_comment: GitHub API already returns newest
  first with sort=created&direction=desc, so reversed() was returning
  the oldest comment instead of the latest

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

无遗漏

PR 标题和 body 列出的 5 项改动均已完整实现:

  1. _filter_noise() — 定义于 run-multi-review.py:445,在 format_pr_comment()(第 487、490 行)和 post_fallback_comment()(第 501 行)中均有调用 ✓
  2. cleanup_reviewer_comments() — 定义于第 640 行,在 coordinator 超时回退路径(第 850 行)和正常发布 synthesis 后(第 876 行)均有调用 ✓
  3. post_pr_comment() 返回 comment ID — 第 555-583 行返回 int | None,返回值在第 848/869 行被捕获并传入 cleanup_reviewer_comments
  4. 保留 USE_GITHUB_TOKEN=true — 第 201 行 _run_opencode() 中直接设置,第 733 行 env_forward 表中也映射了 MULTI_REVIEW_USE_GITHUB_TOKENUSE_GITHUB_TOKEN
  5. 删除 standalone workflowreview.ymlarchitect-review.yml 已删除 ✓

所有新增函数在主流程中均被正确调用,两条路径(coordinator 成功/失败)都调用了 cleanup_reviewer_comments,Comment ID 传递链路完整,无需额外修复。

New%20session%20-%202026-05-21T09%3A56%3A46.238Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

[09:58:29.497] INFO (#276): loop {
service: "session.prompt",
"session.id": "ses_1b6071be8ffe2BJJzeXIkUoP2v",
step: 0,
}
[09:58:29.549] INFO (#290): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
[09:58:29.702] INFO (#276): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
[09:58:40.977] INFO (#276): loop {
service: "session.prompt",
"session.id": "ses_1b6071be8ffe2BJJzeXIkUoP2v",
step: 1,
}
[09:58:40.981] INFO (#276): exiting loop {
service: "session.prompt",
"session.id": "ses_1b6071be8ffe2BJJzeXIkUoP2v",
}


详细审查报告:

✅ security

无遗漏

PR 标题和 body 列出的 5 项改动均已完整实现:

  1. _filter_noise() — 定义于 multi-review/run-multi-review.py:425,在 format_pr_comment()(第 453、456 行)和 post_fallback_comment()(第 467 行)中均有调用
  2. cleanup_reviewer_comments() — 定义于第 567 行,在 coordinator 超时回退路径(第 777 行)和正常发布 synthesis 后(第 803 行)均有调用
  3. post_pr_comment() 返回 comment ID — 第 507 行返回 comment_id,用于 cleanup_reviewer_comments 的精确定位
  4. 保留 USE_GITHUB_TOKEN=true — 第 201 行和 env_forward(第 660 行)均已设置
  5. 删除 standalone workflow.github/workflows/review.ymlarchitect-review.yml 均已删除

所有新增的函数都在主流程中被正确调用,cleanup_reviewer_comments 覆盖了 coordinator 成功和失败两条路径,Comment ID 传递链路完整。

✅ quality

无遗漏

PR 标题和 body 列出的 5 项改动均已完整实现:

  1. _filter_noise() — 定义于 multi-review/run-multi-review.py:425,在 format_pr_comment()(第 453、456 行)和 post_fallback_comment()(第 467 行)中均有调用
  2. cleanup_reviewer_comments() — 定义于第 567 行,在 coordinator 超时回退路径(第 777 行)和正常发布 synthesis 后(第 803 行)均有调用
  3. post_pr_comment() 返回 comment ID — 第 507 行返回 comment_id,用于 cleanup_reviewer_comments 的精确定位
  4. 保留 USE_GITHUB_TOKEN=true — 第 201 行和 env_forward(第 660 行)均已设置
  5. 删除 standalone workflow.github/workflows/review.ymlarchitect-review.yml 均已删除

所有新增的函数都在主流程中被正确调用,cleanup_reviewer_comments 覆盖了 coordinator 成功和失败两条路径,Comment ID 传递链路完整。

The previous approach fetched comments per-reviewer which caused race
conditions in parallel execution -- both reviewers would fetch the same
"latest" comment.

New approach:
1. Snapshot all bot comment IDs before reviewers start
2. Run all reviewers in parallel (no per-reviewer fetch)
3. After all reviewers finish, fetch new bot comments in bulk and
   assign to reviewers by creation time order
4. Snapshot again before coordinator, fetch coordinator's posted
   comment after it finishes
5. Cleanup deletes all opencode-CLI comments, keeps only Python-posted

This also fixes the coordinator output being pure noise -- we now fetch
the coordinator's actual AI synthesis from its posted PR comment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

无遗漏

PR 标题和 body 列出的 5 项改动均已完整实现:

  1. _filter_noise() — 定义于 run-multi-review.py:441,在 format_pr_comment()(第 483、486 行)和 post_fallback_comment()(第 497 行)中均有调用 ✓
  2. cleanup_reviewer_comments() — 定义于第 651 行,在 coordinator 超时回退路径(第 875 行)和正常发布 synthesis 后(第 908 行)均有调用 ✓
  3. post_pr_comment() 返回 comment ID — 第 566 行返回 int | None,返回值在第 873/901 行被捕获并传入 cleanup_reviewer_comments
  4. 保留 USE_GITHUB_TOKEN=true — 第 201 行 _run_opencode() 中直接设置,第 744 行 env_forward 表中也映射了默认值 ✓
  5. 删除 standalone workflowreview.yml(-30 行)和 architect-review.yml(-37 行)已删除,无其他文件引用这两个文件 ✓

所有新增函数在主流程中均被正确调用,两条路径(coordinator 成功/失败)都调用了 cleanup_reviewer_comments,Comment ID 传递链路完整。无需额外修复。

New%20session%20-%202026-05-21T10%3A03%3A47.021Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

有条件合并

总结

security 和 quality 两位 reviewer 均确认 PR 的 5 项改动均已实现。但 quality reviewer 发现一个影响正确性的阻塞 bug。

阻塞项

  1. _fetch_latest_bot_comment()reversed() 导致返回最旧而非最新评论(quality 阻塞项,multi-review/run-multi-review.py:542)— GitHub API 返回的 comments 已是"最新在前",reversed() 后循环第一个匹配就 return,实际返回的是最旧 bot 评论。security 也在建议项中提及此函数存在竞态问题。修复:移除第 542 行的 reversed(),直接遍历 comments。— 多 reviewer 确认

建议项

  1. 死代码(quality)— _filter_noise()if stripped in ("}", "{"): 永不触发,_NOISE_BLOCK_RE 已覆盖。
  2. reviewer 名称未做 HTML 转义(security)— <summary>{r['name']}</summary> 建议使用 html.escape()
  3. MULTI_REVIEW_EXTRA_ENV 任意环境变量注入(security)— 允许设置 PATHLD_PRELOAD 等,建议对 key 做白名单校验。
  4. 静默吞异常(quality)— except Exception: pass 建议至少 print(..., file=sys.stderr)
  5. Comment 删除可能误删(security)— keep_comment_idNone 时可能删错评论。
  6. _run_opencode_raw()_run_opencode_in_dir() 代码重复(quality)— 仅 cwd 不同,可合并。
  7. _fetch_latest_bot_comment() 竞态(security)— 回退路径(_run_reviewer_inner:312)未传 before_comment_id

:后续 quality reviewer 另有结论为"可合并"的摘要(10:05:57),但其详细报告(09:53:11)明确将 reversed() 列为阻塞项,此处采用详细报告的判断。


详细审查报告:

⚠️ security
✅ quality

无遗漏

PR 标题和 body 列出的 5 项改动均已完整实现:

  1. _filter_noise() — 定义于 run-multi-review.py:441,在 format_pr_comment()(第 483、486 行)和 post_fallback_comment()(第 497 行)中均有调用 ✓
  2. cleanup_reviewer_comments() — 定义于第 651 行,在 coordinator 超时回退路径(第 875 行)和正常发布 synthesis 后(第 908 行)均有调用 ✓
  3. post_pr_comment() 返回 comment ID — 第 566 行返回 int | None,返回值在第 873/901 行被捕获并传入 cleanup_reviewer_comments
  4. 保留 USE_GITHUB_TOKEN=true — 第 201 行 _run_opencode() 中直接设置,第 744 行 env_forward 表中也映射了默认值 ✓
  5. 删除 standalone workflowreview.yml(-30 行)和 architect-review.yml(-37 行)已删除,无其他文件引用这两个文件 ✓

所有新增函数在主流程中均被正确调用,两条路径(coordinator 成功/失败)都调用了 cleanup_reviewer_comments,Comment ID 传递链路完整。无需额外修复。

@Svtter
Copy link
Copy Markdown
Collaborator Author

Svtter commented May 21, 2026

Closing this PR. The opencode github run subprocess approach has a fundamental flaw: its stdout contains ONLY CLI boilerplate (permissions, session info, tool calls), never the actual AI review content. After multiple iterations (#87, #90, #98), we're switching to a different approach. See issue for details.

@Svtter Svtter closed this May 21, 2026
Svtter added a commit that referenced this pull request May 22, 2026
* chore: remove multi-review action (fundamental design flaw)

The opencode github run subprocess approach is fundamentally flawed:
stdout contains only CLI boilerplate, never the actual AI review content.
After multiple failed iterations (PR #87, #90, #98), removing this module
to prepare for a replacement approach.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: clean up all multi-review references

- Delete .github/workflows/multi-review.yml
- Delete tests/test_multi_review.py
- Remove multi-review from README.md action list, docs, and usage examples
- Remove multi-review from skills/setup-ci/ documentation and action reference
- Fix architect-review section that was accidentally merged in actions-reference.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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.

1 participant