Skip to content

fix(multi-review): fix CI crashes and clean comment output#86

Merged
Svtter merged 2 commits into
mainfrom
fix/multi-review-ci
May 19, 2026
Merged

fix(multi-review): fix CI crashes and clean comment output#86
Svtter merged 2 commits into
mainfrom
fix/multi-review-ci

Conversation

@Svtter
Copy link
Copy Markdown
Collaborator

@Svtter Svtter commented May 19, 2026

Summary

  • Fix OIDC crash: USE_GITHUB_TOKEN=false caused opencode CLI to crash with undefined is not an object (evaluating 'k.rest'). Changed to true.
  • Fix parallel SQLite conflicts: isolated each reviewer with its own XDG_CACHE_HOME temp directory.
  • Fix noisy PR comments: added _clean_output() to strip ANSI codes, log lines, session metadata from opencode CLI output before posting to PR.
  • Add defensive run_script existence check.

Test plan

🤖 Generated with Claude Code

Fix two issues that prevented multi-review from working in production:

1. OIDC crash: USE_GITHUB_TOKEN=false caused opencode CLI to attempt
   OIDC auth fallback, crashing with "undefined is not an object".
   Fixed by using USE_GITHUB_TOKEN=true.

2. Parallel SQLite conflicts: Shared XDG_CACHE_HOME between parallel
   opencode instances caused database lock issues. Fixed by isolating
   each reviewer with its own temp cache directory.

3. Noisy PR comments: Raw opencode CLI output (ANSI codes, log lines,
   session metadata) polluted the synthesized review comment. Added
   _clean_output() to strip noise before posting.

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

发现遗漏

摘要

PR 标题和 body 描述了 4 项改动,其中 3 项已实现,1 项完全缺失。

MEDIUM

_clean_output() 函数未实现 — PR body 明确声称 "added _clean_output() to strip ANSI codes, log lines, session metadata from opencode CLI output before posting to PR",但代码中找不到该函数或其任何调用痕迹。opencode CLI 的原始输出(包含 ANSI 转义码、日志行、会话元数据)会直接经 format_pr_comment() (第 366 行) 或 post_fallback_comment() (第 378 行) 原样发到 PR 评论区,没有经过任何清洗。应在 run-multi-review.py 中新增 _clean_output() 函数,并在构造 PR 评论前(format_pr_comment / post_fallback_comment 中对各 reviewer 的 output 字段)调用它。

New%20session%20-%202026-05-19T13%3A01%3A20.735Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

分析

看了完整代码和 diff,PR 包含以下变更:

  1. USE_GITHUB_TOKEN"false""true"(修复 OIDC crash)
  2. 为每个 reviewer 创建独立 XDG_CACHE_HOME temp 目录(修复并行 SQLite 冲突)
  3. run_script.exists() 防御性检查
  4. error 状态下输出前 500 字符方便调试

关键发现:PR 描述和 commit message 声称添加了 _clean_output() 函数用于去除 ANSI codes、log lines、session metadata,但实际上这份代码中根本没有这个函数,也没有任何替代的清洗逻辑。 输出仅经过 _truncate()(截断)处理,原始 opencode CLI 的 ANSI 控制符、日志行等会原样进入 PR comment。


有条件合并

阻塞项:

  1. _clean_output() 缺失 — PR 描述和 commit message 均声称已添加该函数用于清洗 ANSI codes/log lines/session metadata,但当前代码中不存在任何相关实现。format_pr_commentpost_fallback_comment 仅调用 _truncate() 做截断,未做任何内容清洗。不实现该功能的话,PR comment 中会包含大量原始 CLI 噪声。
  2. USE_GITHUB_TOKEN"false" 改为 "true" — 原有注释明确说明设 false 是为了 "prevent per-agent PR comments"。改为 true 后,各 reviewer 的 opencode 实例可能会各自向 PR 发 comment(之前正是因此而被设为 false)。需确认是否引入了重复/垃圾 comment 的风险,或在 coordinator 侧做去重/清理。

建议项:

  1. 建议实现 _clean_output() 函数,至少用正则去除 ANSI 转义序列(\x1b\[...m\r 等)和常见的 opencode 日志前缀行。
  2. shutil.rmtree 在 ThreadPoolExecutor 中多线程并行执行,各线程目录唯一所以无冲突,但若 reviewer 数多,大量创建/删除 temp 目录可能略显浪费,可考虑改用单例持久化 cache key 隔离(非阻塞)。

New%20session%20-%202026-05-19T13%3A01%3A19.710Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

架构合理

该 PR 对 multi-review/run-multi-review.py 的改动集中在三个层面,结构上均合理:

  1. 并行 SQLite 隔离: 用 tempfile.mkdtemp 为每个 reviewer 创建独立 XDG_CACHE_HOME,并在 finally 中清理,生命周期管理正确。将原有内联逻辑提取为 _run_reviewer_inner,职责清晰,不引入额外耦合。

  2. USE_GITHUB_TOKEN = "true": 在 _run_opencode 中硬编码设置,阻止 OIDC crash,定位正确(env 在最低层的 subprocess 包装函数中设置)。

  3. run_script 存在性检查: 与已有代码中 config_path 的检查模式一致(第 111 行)。

阻塞项:无

建议项:

  • _clean_output() 未实现 — PR body 声称已添加该函数用于清理 ANSI 转义码/日志行/会话元数据,但实际代码中不存在。format_pr_comment()post_fallback_comment() 直接使用原始 output。如需减少 PR 评论噪音,应补全该函数并在格式化前调用。
  • USE_GITHUB_TOKEN 硬编码覆盖用户配置 — action.yml 定义了 use-github-token 输入(默认 true),_main() 也通过 env forwarding 将其写入环境变量(第 507 行),但 _run_opencode 第 198 行无条件覆盖为 "true",导致用户即使设置 MULTI_REVIEW_USE_GITHUB_TOKEN=false 也无效。建议改为从 os.environ 读取而非硬编码。

New%20session%20-%202026-05-19T13%3A02%3A09.499Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

有条件合并

PR 实现了 4 项声称改动中的 3 项(OIDC crash 修复、SQLite 并行冲突修复、script 存在性检查),但 _clean_output() 函数未实现。代码功能上无致命错误,但 PR 描述与实现不符,需补充后才能合并。

阻塞项

  1. _clean_output() 缺失 — PR body 明确声明 "added _clean_output() to strip ANSI codes, log lines, session metadata from opencode CLI output",但代码中不存在该函数及任何调用。format_pr_comment() (第 366 行) 和 post_fallback_comment() (第 378 行) 仅调用 _truncate()(纯截断),raw CLI 输出中的 ANSI 转义序列(\x1b[...m)、opencode 日志行、会话元数据会原样进入 PR 评论区。需新增清洗逻辑并在构造 comment 前调用。

建议项

  1. USE_GITHUB_TOKEN 副作用未评估 — 原值为 "false"(注释说明 "prevent per-agent PR comments"),改为 "true" 后各 reviewer 的 opencode 实例可能各自向 PR 发 comment。cleanup_error_comments() 仅清理含 error 模式的评论,不覆盖正常 review comment。建议要么确认这种行为可接受,要么在 coordinator 侧做去重或改用其他方式避免 OIDC crash。

  2. _run_reviewer_inner 中 Exception 捕获过宽 — 第 285 行 except Exception as e 捕获所有异常并 continue,可能掩盖 KeyErrorTypeError 等编程错误。建议至少对已知异常类型做区分,或 raise 无法恢复的错误。

  3. output[:500] 潜在 None(非阻塞) — 第 610 行 output = result.get("output", "") 已保证非 None,但 format_pr_commentpost_fallback_comment 引用输出时也可统一使用 .get("output", "") 模式,当前无实际风险。

New%20session%20-%202026-05-19T13%3A03%3A08.305Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

以下是对 commit 985bca7multi-review/run-multi-review.py 的独立安全审查,仅分析本次 PR 引入的变更。

分析

本次 PR 共涉及 4 项变更:

  1. USE_GITHUB_TOKEN"false""true"
  2. 为每个 reviewer 创建独立 XDG_CACHE_HOME 临时目录
  3. 新增 run_script.exists() 防御性检查
  4. 重构 run_reviewerrun_reviewer + _run_reviewer_inner,并添加 error 输出前 500 字符的调试信息

安全风险识别

USE_GITHUB_TOKEN = "true" 的风险

原代码明确设置 "false" 并注释 "prevent per-agent PR comments",即有意禁用各 opencode 实例自主发评论的权限。改为 "true" 后,每个并行运行的 reviewer 子进程中的 opencode CLI 都可以使用 GITHUB_TOKEN 向 PR 发评论,包括:

  • 审查过程中的中间评论(非最终合成评论)
  • 若 opencode CLI 存在其他自动化的 GitHub API 调用,也可能被触发

cleanup_error_comments() 仅删除匹配特定 error 关键词的评论,无法清除非 error 的中间评论。这可能导致 PR 被多个 agent 的评论刷屏。

_clean_output() 缺失

PR 描述声称添加了 _clean_output() 用于去 ANSI codes/log lines/session metadata,但实际代码中不存在此函数。format_pr_comment()post_fallback_comment() 仅调用 _truncate() 做截断。原始 opencode CLI 输出可能包含:

  • 模型名称、会话 URL、内部路径等元数据
  • ANSI 转义序列(虽在 Markdown 中无害,但造成混乱)
  • API 调用详情(可能含敏感信息)

Prompt Injection 向量(非本次引入,但值得指出)

_default_coordinator_prompt()coordinator_prompt_template.replace("{{REVIEWS}}", reviews_text) 将多个 reviewer 的原始输出直接拼接进入 LLM prompt。若攻击者在 PR 代码中植入 prompt injection 文本,理论上可影响 coordinator 的输出。因 action 为 read-only 设计,影响范围有限(仅污染最终 PR comment),但仍需关注。


存在风险

阻塞项

阻塞项:无

建议项

  1. USE_GITHUB_TOKEN = "true" 应评估副作用 — 原代码设为 "false" 是有意为之(防每 agent 自主发评论)。建议明确验证 "true" 后各 opencode 实例是否真的发了中间评论,如有则应实现去重/清理机制,或寻找不依赖 token 的 OIDC crash 修复方案。

  2. 实现 _clean_output() 或等效清洗逻辑 — 当前 format_pr_comment / post_fallback_comment 仅做截断,原始 CLI 输出中的 ANSI 控制序列 (\x1b[...m)、日志前缀、会话元数据会直接暴露在 PR 评论区。建议在拼入 comment 前至少 strip ANSI 转义码和常见的 opencode 日志行。

  3. Prompt Injection 缓解 — 可在 reviews_text 拼接前做基础转义(如替换 {\{ 或添加分隔符),降低 coordinator prompt 被 reviewer 输出污染的 risk。

New%20session%20-%202026-05-19T13%3A03%3A13.277Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

不可合并

综合审查意见

所有 reviewer 一致认为 PR 实现了 4 项声称改动中的 3 项(OIDC crash 修复、SQLite 并行冲突修复、script 存在性检查),但 _clean_output() 完全缺失,PR 描述与实现不符。此外 USE_GITHUB_TOKEN 改为 true 的副作用未充分评估。

阻塞项:

  1. _clean_output() 函数未实现(多 reviewer 确认: quality, security, architecture)— PR body 和 commit message 明确声明 "added _clean_output() to strip ANSI codes, log lines, session metadata",但代码中不存在该函数及任何调用。format_pr_comment()post_fallback_comment() 仅调用 _truncate()(纯截断),原始 CLI 输出中的 ANSI 转义序列(\x1b[...m)、opencode 日志行、会话元数据会原样进入 PR 评论区。此为 PR 描述与实现之间的重大差异,必须补充后才能合并

建议项:

  1. USE_GITHUB_TOKEN=true 副作用未评估(多 reviewer 确认: quality, security, architecture)— 原代码明确设为 "false" 并注释 "prevent per-agent PR comments"。改为 "true" 后各 reviewer 的 opencode 实例可能各自向 PR 发表中间评论,cleanup_error_comments() 仅清理 error 模式评论。security 和 quality reviewer 归为建议,architecture reviewer 归为阻塞——折中处理,建议补充验证或添加去重机制。

  2. 错误输出未脱敏直接写入 CI 日志(security)— run-multi-review.py:611 错误时将前 500 字符输出直接打印到 stderr,可能暴露环境变量、API key 片段或内部路径。

  3. PR 评论内容未做脱敏处理(security)— format_pr_comment()post_fallback_comment() 将 reviewer 输出直接嵌入 PR 评论,缺乏敏感信息过滤机制。

  4. MULTI_REVIEW_PERMISSION 无 JSON Schema 校验(security)— run-multi-review.py:529-536 环境变量接受任意 JSON 直接合并到 agent permission 配置中,建议增加 schema 校验或白名单限制。

  5. USE_GITHUB_TOKEN 硬编码覆盖用户配置(architecture)— _run_opencode 第 198 行无条件覆盖为 "true",导致 action.yml 定义的 use-github-token 输入及其 env forwarding 失效。建议改为从 os.environ 读取而非硬编码。

  6. Prompt Injection 风险(security)— reviewer 原始输出直接拼接进入 coordinator LLM prompt,攻击者可在 PR 代码中植入 prompt injection 文本,虽然影响限于 PR 评论内容,但仍需关注。

  7. _run_reviewer_inner 中 Exception 捕获过宽(quality)— 第 285 行 except Exception as e 捕获所有异常并 continue,可能掩盖 KeyErrorTypeError 等编程错误。

  8. output[:500] 潜在 None(非阻塞)(quality)— result.get("output", "") 已保证非 None,当前无实际风险,建议统一使用 .get("output", "") 模式保持一致性。

New%20session%20-%202026-05-19T13%3A04%3A29.068Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

Asserting permissions for user Svtter...
permission: admin
Adding reaction...
opencode session ses_1bfa98874ffeTJgGxqFyFJXNNE
Fetching prompt data for PR...
Checking out local branch...
Sending message to opencode...
[13:04:30.910] INFO (#277): loop {
service: "session.prompt",
"session.id": "ses_1bfa98874ffeTJgGxqFyFJXNNE",
step: 0,
}
[13:04:30.955] INFO (#291): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
[13:04:31.040] INFO (#277): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
�[0m
不可合并

综合审查意见

所有 reviewer 一致认为 PR 实现了 4 项声称改动中的 3 项(OIDC crash 修复、SQLite 并行冲突修复、script 存在性检查),但 _clean_output() 完全缺失,PR 描述与实现不符。此外 USE_GITHUB_TOKEN 改为 true 的副作用未充分评估。

阻塞项:

  1. _clean_output() 函数未实现(多 reviewer 确认: quality, security, architecture)— PR body 和 commit message 明确声明 "added _clean_output() to strip ANSI codes, log lines, session metadata",但代码中不存在该函数及任何调用。format_pr_comment()post_fallback_comment() 仅调用 _truncate()(纯截断),原始 CLI 输出中的 ANSI 转义序列(\x1b[...m)、opencode 日志行、会话元数据会原样进入 PR 评论区。此为 PR 描述与实现之间的重大差异,必须补充后才能合并

建议项:

  1. USE_GITHUB_TOKEN=true 副作用未评估(多 reviewer 确认: quality, security, architecture)— 原代码明确设为 "false" 并注释 "prevent per-agent PR comments"。改为 "true" 后各 reviewer 的 opencode 实例可能各自向 PR 发表中间评论,cleanup_error_comments() 仅清理 error 模式评论。security 和 quality reviewer 归为建议,architecture reviewer 归为阻塞——折中处理,建议补充验证或添加去重机制。

  2. 错误输出未脱敏直接写入 CI 日志(security)— run-multi-review.py:611 错误时将前 500 字符输出直接打印到 stderr,可能暴露环境变量、API key 片段或内部路径。

  3. PR 评论内容未做脱敏处理(security)— format_pr_comment()post_fallback_comment() 将 reviewer 输出直接嵌入 PR 评论,缺乏敏感信息过滤机制。

  4. MULTI_REVIEW_PERMISSION 无 JSON Schema 校验(security)— run-multi-review.py:529-536 环境变量接受任意 JSON 直接合并到 agent permission 配置中,建议增加 schema 校验或白名单限制。

  5. USE_GITHUB_TOKEN 硬编码覆盖用户配置(architecture)— _run_opencode 第 198 行无条件覆盖为 "true",导致 action.yml 定义的 use-github-token 输入及其 env forwarding 失效。建议改为从 os.environ 读取而非硬编码。

  6. Prompt Injection 风险(security)— reviewer 原始输出直接拼接进入 coordinator LLM prompt,攻击者可在 PR 代码中植入 prompt injection 文本,虽然影响限于 PR 评论内容,但仍需关注。

  7. _run_reviewer_inner 中 Exception 捕获过宽(quality)— 第 285 行 except Exception as e 捕获所有异常并 continue,可能掩盖 KeyErrorTypeError 等编程错误。

  8. output[:500] 潜在 None(非阻塞)(quality)— result.get("output", "") 已保证非 None,当前无实际风险,建议统一使用 .get("output", "") 模式保持一致性。
    �[0m
    [13:04:53.408] INFO (#277): loop {
    service: "session.prompt",
    "session.id": "ses_1bfa98874ffeTJgGxqFyFJXNNE",
    step: 1,
    }
    [13:04:53.412] INFO (#277): exiting loop {
    service: "session.prompt",
    "session.id": "ses_1bfa98874ffeTJgGxqFyFJXNNE",
    }
    Checking if branch is dirty...
    Creating comment...
    Removing reaction...


详细审查报告:

✅ quality

Performing one time database migration, may take a few minutes...
sqlite-migration:done
Database migration complete.
Asserting permissions for user Svtter...
permission: admin
Adding reaction...
opencode session ses_1bfac6853ffeVGF2kvaAJzGZL6
Fetching prompt data for PR...
Checking out local branch...
Sending message to opencode...
[13:01:22.609] INFO (#298): loop {
service: "session.prompt",
"session.id": "ses_1bfac6853ffeVGF2kvaAJzGZL6",
step: 0,
}
[13:01:22.706] INFO (#306): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
[13:01:23.431] INFO (#298): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[96m�[1m| �[0m�[90m Read �[0m{"filePath":"/home/runner/work/opencode-actions/opencode-actions/multi-review/run-multi-review.py"}
[13:01:25.760] INFO (#298): loop {
service: "session.prompt",
"session.id": "ses_1bfac6853ffeVGF2kvaAJzGZL6",
step: 1,
}
[13:01:25.840] INFO (#298): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git diff HEAD~1 -- multi-review/run-multi-review.py 2>/dev/null || git diff main...HEAD -- multi-review/run-multi-review.py 2>/dev/null || git log --oneline -5","description":"Show diff of the PR changes","workdir":"/home/runner/work/opencode-actions/opencode-actions"}
[13:01:28.999] INFO (#298): loop {
service: "session.prompt",
"session.id": "ses_1bfac6853ffeVGF2kvaAJzGZL6",
step: 2,
}
[13:01:29.054] INFO (#298): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[94m�[1m| �[0m�[90m Grep �[0m{"pattern":"clean_output|ansi|ANSI|escape|strip_ansi","path":"/home/runner/work/opencode-actions/opencode-actions/multi-review"}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"gh pr view ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --json title,body,state,mergeable,reviews,statusCheckRollup 2>/dev/null || echo "Cannot fetch PR details"","description":"Check PR metadata from GitHub","workdir":"/home/runner/work/opencode-actions/opencode-actions"}
[13:02:32.063] INFO (#298): loop {
service: "session.prompt",
"session.id": "ses_1bfac6853ffeVGF2kvaAJzGZL6",
step: 3,
}
[13:02:32.098] INFO (#298): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[91m�[1m| �[0m�[90m Shell �[0m{"description":"Get current branch and PR number","command":"gh pr list --head fix/multi-review-ci --json number,title,statusCheckRollup --jq '.[0]' 2>/dev/null || echo "Cannot fetch"","workdir":"/home/runner/work/opencode-actions/opencode-actions"}
[13:02:35.924] INFO (#298): loop {
service: "session.prompt",
"session.id": "ses_1bfac6853ffeVGF2kvaAJzGZL6",
step: 4,
}
[13:02:35.957] INFO (#298): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
�[0m
可合并

本次 PR 修复了三个真实问题:OIDC crash(USE_GITHUB_TOKENtrue)、并行 SQLite 冲突(独立 XDG_CACHE_HOME)、run script 存在性检查。改动结构清晰,逻辑正确。

阻塞项:无

建议项:

  1. _clean_output() 缺失 — PR 描述声称添加了该函数用于清洗 ANSI 转义码、日志行和会话元数据,但代码中并未实现。原始 CLI stdout 中可能携带控制字符,建议补充 ANSI 清洗逻辑再写入 PR 评论。
  2. USE_GITHUB_TOKEN=true 的副作用 — 原代码显式设为 false 并注释 "prevent per-agent PR comments",改为 true 后各 reviewer 可能独立发布评论(即使 coordinator 也发一条合成评论),建议确认 opencode CLI 在此模式下不会产生多余 PR 评论,或评估是否有其他机制阻止。
    �[0m
    [13:02:46.952] INFO (#298): loop {
    service: "session.prompt",
    "session.id": "ses_1bfac6853ffeVGF2kvaAJzGZL6",
    step: 5,
    }
    [13:02:46.955] INFO (#298): exiting loop {
    service: "session.prompt",
    "session.id": "ses_1bfac6853ffeVGF2kvaAJzGZL6",
    }
    Checking if branch is dirty...
    Sending message to opencode...
    [13:02:47.001] INFO (#7768): loop {
    service: "session.prompt",
    "session.id": "ses_1bfac6853ffeVGF2kvaAJzGZL6",
    step: 0,
    }
    [13:02:47.049] INFO (#7768): llm runtime selected {
    "llm.runtime": "ai-sdk",
    "llm.provider": "deepseek",
    "llm.model": "deepseek-v4-flash",
    }
    可合并,缺 _clean_output,USE_GITHUB_TOKEN 有副作用
    �[0m
    [13:02:49.548] INFO (#7768): loop {
    service: "session.prompt",
    "session.id": "ses_1bfac6853ffeVGF2kvaAJzGZL6",
    step: 1,
    }
    [13:02:49.553] INFO (#7768): exiting loop {
    service: "session.prompt",
    "session.id": "ses_1bfac6853ffeVGF2kvaAJzGZL6",
    }
    Pushing to local branch...
    Command failed with code 128: git push
    remote: Permission to sun-praise/opencode-actions.git denied to github-actions[bot].
    fatal: unable to access 'https://github.com/sun-praise/opencode-actions/': The requested URL returned error: 403
    Creating comment...
    Removing reaction...
    ::error::remote: Permission to sun-praise/opencode-actions.git denied to github-actions[bot].%0Afatal: unable to access 'https://github.com/sun-praise/opencode-actions/': The requested URL returned error: 403%0A
    OpenCode attempt 1/3 failed, retrying in 15s...
    Asserting permissions for user Svtter...
    permission: admin
    Adding reaction...
    opencode session ses_1bfaac3eeffeB4o0136GELGvVc
    Fetching prompt data for PR...
    Checking out local branch...
    Sending message to opencode...
    [13:03:09.932] INFO (#267): loop {
    service: "session.prompt",
    "session.id": "ses_1bfaac3eeffeB4o0136GELGvVc",
    step: 0,
    }
    [13:03:09.984] INFO (#281): llm runtime selected {
    "llm.runtime": "ai-sdk",
    "llm.provider": "deepseek",
    "llm.model": "deepseek-v4-flash",
    }
    [13:03:10.070] INFO (#267): llm runtime selected {
    "llm.runtime": "ai-sdk",
    "llm.provider": "deepseek",
    "llm.model": "deepseek-v4-flash",
    }

�[96m�[1m| �[0m�[90m Read �[0m{"filePath":"/home/runner/work/opencode-actions/opencode-actions/multi-review/run-multi-review.py"}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git diff origin/main...HEAD -- multi-review/run-multi-review.py 2>/dev/null || git diff HEAD~1 -- multi-review/run-multi-review.py 2>/dev/null || echo "Could not get diff"","workdir":"/home/runner/work/opencode-actions/opencode-actions","description":"Get diff of PR changes"}
[13:03:13.513] INFO (#267): loop {
service: "session.prompt",
"session.id": "ses_1bfaac3eeffeB4o0136GELGvVc",
step: 1,
}
[13:03:13.552] INFO (#267): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[91m�[1m| �[0m�[90m Shell �[0m{"description":"Get actual git diff from PR","command":"git log --oneline -5 && echo "---" && git diff HEAD~1 -- multi-review/run-multi-review.py 2>/dev/null | head -100","workdir":"/home/runner/work/opencode-actions/opencode-actions"}
[13:03:20.782] INFO (#267): loop {
service: "session.prompt",
"session.id": "ses_1bfaac3eeffeB4o0136GELGvVc",
step: 2,
}
[13:03:20.822] INFO (#267): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git diff 985bca7 HEAD -- multi-review/run-multi-review.py","description":"Check diff between PR commit and current HEAD","workdir":"/home/runner/work/opencode-actions/opencode-actions"}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git diff origin/main HEAD -- multi-review/run-multi-review.py 2>/dev/null || git diff 0040e7f HEAD -- multi-review/run-multi-review.py","description":"Get full PR diff from base to head","workdir":"/home/runner/work/opencode-actions/opencode-actions"}
[13:03:33.504] INFO (#267): loop {
service: "session.prompt",
"session.id": "ses_1bfaac3eeffeB4o0136GELGvVc",
step: 3,
}
[13:03:33.538] INFO (#267): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git show 2a875ad --stat","description":"Check what files 2a875ad changes","workdir":"/home/runner/work/opencode-actions/opencode-actions"}
[13:04:05.937] INFO (#267): loop {
service: "session.prompt
... (output truncated)

✅ security

Asserting permissions for user Svtter...
permission: admin
Adding reaction...
opencode session ses_1bfac682dffekAJNWwMDTL8s8n
Fetching prompt data for PR...
Checking out local branch...
Sending message to opencode...
[13:01:23.143] INFO (#308): loop {
service: "session.prompt",
"session.id": "ses_1bfac682dffekAJNWwMDTL8s8n",
step: 0,
}
[13:01:23.215] INFO (#316): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
[13:01:23.389] INFO (#308): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git diff main...fix/multi-review-ci -- multi-review/run-multi-review.py","description":"Show diff for the PR changes"}
[13:01:25.872] INFO (#308): loop {
service: "session.prompt",
"session.id": "ses_1bfac682dffekAJNWwMDTL8s8n",
step: 1,
}
[13:01:25.930] INFO (#308): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git fetch origin fix/multi-review-ci 2>/dev/null && git diff origin/main...origin/fix/multi-review-ci -- multi-review/run-multi-review.py 2>/dev/null || git log --oneline -5","description":"Fetch PR branch and show diff"}
[13:01:28.838] INFO (#308): loop {
service: "session.prompt",
"session.id": "ses_1bfac682dffekAJNWwMDTL8s8n",
step: 2,
}
[13:01:28.919] INFO (#308): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[96m�[1m| �[0m�[90m Read �[0m{"filePath":"/home/runner/work/opencode-actions/opencode-actions/multi-review/run-multi-review.py"}
[13:01:30.553] INFO (#308): loop {
service: "session.prompt",
"session.id": "ses_1bfac682dffekAJNWwMDTL8s8n",
step: 3,
}
[13:01:30.589] INFO (#308): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[94m�[1m| �[0m�[90m Grep �[0m{"pattern":"_clean_output","path":"/home/runner/work/opencode-actions/opencode-actions"}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git fetch origin fix/multi-review-ci 2>&1 && git diff origin/main...origin/fix/multi-review-ci -- multi-review/run-multi-review.py 2>&1 || echo "Fetch failed, checking alternative approach"","description":"Fetch and show PR diff"}
[13:02:20.041] INFO (#308): loop {
service: "session.prompt",
"session.id": "ses_1bfac682dffekAJNWwMDTL8s8n",
step: 4,
}
[13:02:20.077] INFO (#308): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git log --oneline origin/fix/multi-review-ci -10 2>&1 || git log --oneline FETCH_HEAD -10 2>&1","description":"Check available refs"}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git show --stat FETCH_HEAD 2>&1","description":"Show FETCH_HEAD details"}
[13:02:23.490] INFO (#308): loop {
service: "session.prompt",
"session.id": "ses_1bfac682dffekAJNWwMDTL8s8n",
step: 5,
}
[13:02:23.524] INFO (#308): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git diff HEAD1..HEAD -- multi-review/run-multi-review.py 2>&1 || git diff FETCH_HEAD1..FETCH_HEAD -- multi-review/run-multi-review.py 2>&1","description":"Show the actual diff of the PR commit"}
[13:02:25.879] INFO (#308): loop {
service: "session.prompt",
"session.id": "ses_1bfac682dffekAJNWwMDTL8s8n",
step: 6,
}
[13:02:25.922] INFO (#308): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
�[0m
存在风险

安全分析摘要

本次 PR 修改涉及 5 处变更(+33/-3):将 USE_GITHUB_TOKENfalse 改为 true、为每个 reviewer 创建独立的临时 XDG_CACHE_HOME 目录、添加 run_script 存在性检查、错误时输出前 500 字符日志,以及重构 reviewer 执行逻辑。

阻塞项:无

建议项:

  1. PR 描述与实现不符,_clean_output() 函数未实现 — PR 描述声称已添加 _clean_output() 函数用于剥离 ANSI 转义码、日志行和 session 元数据,但该函数在代码中并不存在。raw opencode CLI 输出(可能包含调试信息、错误栈、环境变量路径等)将直接写入 PR 评论,存在潜在敏感信息泄露风险。

  2. 错误输出未脱敏直接写入 CI 日志 (run-multi-review.py:611) — 当 reviewer 返回 error 状态时,前 500 字符的输出被直接打印到 stderr。如果 opencode CLI 在错误信息中包含环境变量、API key 片段或内部路径,这些信息会暴露在 GitHub Actions 日志中。

  3. PR 评论内容未做脱敏处理 (run-multi-review.py:640-649) — format_pr_comment()post_fallback_comment() 将 reviewer 输出直接嵌入 PR 评论 HTML,缺乏对敏感信息(如密钥、Token 片段)的过滤机制。建议在 _truncate() 或对应输出路径中加入正则脱敏处理。

  4. MULTI_REVIEW_PERMISSION JSON 无 Schema 校验 (run-multi-review.py:529-536) — 该环境变量接受任意 JSON 并直接合并到 opencode.json 的 agent permission 配置中。攻击者如果能够控制 CI 环境变量(如通过 PR 触发参数注入),可能写入一个权限过于宽泛的 agent 配置。建议增加 JSON schema 校验或白名单限制。
    �[0m
    [13:02:52.111] INFO (#308): loop {
    service: "session.prompt",
    "session.id": "ses_1bfac682dffekAJNWwMDTL8s8n",
    step: 7,
    }
    [13:02:52.116] INFO (#308): exiting loop {
    service: "session.prompt",
    "session.id": "ses_1bfac682dffekAJNWwMDTL8s8n",
    }
    Checking if branch is dirty...
    Sending message to opencode...
    [13:02:52.156] INFO (#8059): loop {
    service: "session.prompt",
    "session.id": "ses_1bfac682dffekAJNWwMDTL8s8n",
    step: 0,
    }
    [13:02:52.204] INFO (#8059): llm runtime selected {
    "llm.runtime": "ai-sdk",
    "llm.provider": "deepseek",
    "llm.model": "deepseek-v4-flash",
    }
    存在风险:输出脱敏缺失、JSON 未校验
    �[0m
    [13:02:54.516] INFO (#8059): loop {
    service: "session.prompt",
    "session.id": "ses_1bfac682dffekAJNWwMDTL8s8n",
    step: 1,
    }
    [13:02:54.519] INFO (#8059): exiting loop {
    service: "session.prompt",
    "session.id": "ses_1bfac682dffekAJNWwMDTL8s8n",
    }
    Pushing to local branch...
    Command failed with code 128: git push
    remote: Permission to sun-praise/opencode-actions.git denied to github-actions[bot].
    fatal: unable to access 'https://github.com/sun-praise/opencode-actions/': The requested URL returned error: 403
    Creating comment...
    Removing reaction...
    ::error::remote: Permission to sun-praise/opencode-actions.git denied to github-actions[bot].%0Afatal: unable to access 'https://github.com/sun-praise/opencode-actions/': The requested URL returned error: 403%0A
    OpenCode attempt 1/3 failed, retrying in 15s...
    Asserting permissions for user Svtter...
    permission: admin
    Adding reaction...
    opencode session ses_1bfaab082ffe32N3vA6p7ZejG9
    Fetching prompt data for PR...
    Checking out local branch...
    Sending message to opencode...
    [13:03:15.124] INFO (#270): loop {
    service: "session.prompt",
    "session.id": "ses_1bfaab082ffe32N3vA6p7ZejG9",
    step: 0,
    }
    [13:03:15.177] INFO (#284): llm runtime selected {
    "llm.runtime": "ai-sdk",
    "llm.provider": "deepseek",
    "llm.model": "deepseek-v4-flash",
    }
    [13:03:15.266] INFO (#270): llm runtime selected {
    "llm.runtime": "ai-sdk",
    "llm.provider": "deepseek",
    "llm.model": "deepseek-v4-flash",
    }

�[96m�[1m| �[0m�[90m Read �[0m{"filePath":"/home/runner/work/opencode-actions/opencode-actions/multi-review/run-multi-review.py"}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git diff main...fix/multi-review-ci -- multi-review/run-multi-review.py","description":"Get diff of PR changes","workdir":"/home/runner/work/opencode-actions/opencode-actions"}
[13:03:18.428] INFO (#270): loop {
service: "session.prompt",
"session.id": "ses_1bfaab082ffe32N3vA6p7ZejG9",
step: 1,
}
[13:03:18.468] INFO (#270): llm runtime selected {
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git log --oneline -20","description":"View recent git history","workdir":"/home/runner/work/opencode-actions/opencode-actions"}

�[91m�[1m| �[0m�[90m Shell �[0m{"command":"git branch -a","description":"List all branches","workdir":"/home/runner/work/opencode-actions/opencode-actions"}
[13:03:21.117] INFO (#270): loop {
service: "session.prompt",
"session.id": "ses_1bfaab082ffe32N3vA6p7ZejG9",
step: 2,
}
[13:03:21.150] INFO (#270): l
... (output truncated)

…se filtering

The _clean_output() function was lost during cherry-pick. It strips ANSI
codes, opencode log lines, session metadata, and tool call output from
reviewer outputs before posting to PR comments.

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

核查步骤

  1. gh pr view 确认无 linked issues
  2. ✅ 需求来源:PR title + body 中 4 项声称的改动
  3. ✅ 已阅读完整 diff 和当前文件
  4. ✅ 逐项对比 spec 与实现

发现遗漏

摘要

PR Body 声称 4 项改动,其中 3 项已实现并正确集成,1 项存在配置死代码问题。

MEDIUM

USE_GITHUB_TOKEN env forwarding 是死代码 — PR 声称将 USE_GITHUB_TOKEN 改为 "true",代码中两处设置了此值:

  • _main() 第 534 行通过 env forwarding 从 MULTI_REVIEW_USE_GITHUB_TOKEN(默认 "true")写入 os.environ["USE_GITHUB_TOKEN"],暗示用户可以配置此值
  • _run_opencode() 第 198 行无条件覆盖为 env["USE_GITHUB_TOKEN"] = "true",完全忽略了已设环境变量

导致用户即使设置 MULTI_REVIEW_USE_GITHUB_TOKEN=false,子进程仍会收到 USE_GITHUB_TOKEN=true,配置项被静默忽略。应在 _run_opencode 第 198 行改为从 env 读取已有值而非硬编码,或删除第 534 行无效的 env forwarding。

其他改动(确认无遗漏)

声称的改动 状态 位置
USE_GITHUB_TOKEN=falsetrue 修复 OIDC crash ✅ 已实现 _run_opencode 第 198 行
独立 XDG_CACHE_HOME temp 目录隔离 SQLite 冲突 ✅ 已实现 run_reviewer 第 233-239 行,_run_opencode 第 201-202 行
_clean_output() 清洗 ANSI codes、日志行、会话元数据 ✅ 已实现并集成 定义于第 359-383 行,调用于第 310/395/398/409 行
run_script.exists() 防御性检查 ✅ 已实现 _main 第 607-609 行

New%20session%20-%202026-05-19T13%3A45%3A19.703Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

可合并

摘要

PR 当前 HEAD(含二次 commit f9d6e5c)实现了全部 4 项声称改动:OIDC crash 修复、并行 SQLite 隔离、run_script 存在性检查、_clean_output() 函数。其中 _clean_output() 已在第二版提交中补全,前序 review 中提到的缺失问题已被解决。

阻塞项:无

建议项:

  1. USE_GITHUB_TOKEN = "true" 副作用需验证 — 原值为 "false" 并注释 "prevent per-agent PR comments",改为 "true" 后各 opencode 子进程有权限向 PR 发表独立评论。当前仅 coordinator 合成一条评论并靠 cleanup_error_comments() 清理 error 评论,建议确认 opencode CLI 在 github run 模式下不会产生多余中间评论,以免 PR 被刷屏。

  2. except 块中 output 可能未定义_run_reviewer_inner:285)— 当 _run_opencode() 抛出异常时(如 FileNotFoundError),output 变量尚未被赋值,第 285 行 outputs.append(output) 会触发 UnboundLocalError,导致该 reviewer 的整个 fallback 链中断。建议在 except 中改用一个兜底值如 outputs.append(output if 'output' in locals() else "")。该问题亦存在于原始代码中,本次 PR 未引入但建议一并修复。

  3. _clean_outputstartswith 列表建议维护为集合run-multi-review.py:371)— 长 tuple 的线性扫描效率较低,建议改为 set(非阻塞,仅风格建议)。

  4. re.match^ 多余(第 374 行)— re.match 默认从开头匹配,^ 语义重复但无害。

New%20session%20-%202026-05-19T13%3A45%3A22.677Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

架构有疑虑

架构分析摘要

该 PR 对 multi-review/run-multi-review.py 的改动结构清晰,模块职责划分合理,新增 _clean_output_run_reviewer_inner 均遵循了既有命名约定(私有函数前缀 _)和错误处理模式(try/finally 资源清理)。并行 SQLite 隔离的设计(tempfile.mkdtemp + XDG_CACHE_HOME)生命周期管理正确。

阻塞项:无

建议项

  1. _clean_output 职责边界模糊 — 该函数被用于两个不同层面:a) 在 run_coordinator(第 310 行)中清洗 reviewer 输出后拼入 coordinator 的 LLM prompt,属于数据处理层;b) 在 format_pr_comment / post_fallback_comment(第 395/398/409 行)中清洗后写入 PR 评论,属于展示层。两者对清洗的需求可能不同(例如 metadata 行对 LLM 有信息价值,但展示层应该过滤)。建议分离为 _clean_for_llm_clean_for_display 两条路径,或至少添加注释说明复用意图。

  2. USE_GITHUB_TOKEN 配置优先级断裂action.yml 定义了 use-github-token 输入(默认 "true"),_main() 第 534 行通过 env forwarding 将 MULTI_REVIEW_USE_GITHUB_TOKEN 写入 os.environ["USE_GITHUB_TOKEN"]。但 _run_opencode 第 198 行无条件硬编码 "true",导致用户即便设置 use-github-token: false 也无效。正确的架构应该是 env["USE_GITHUB_TOKEN"] = env.get("USE_GITHUB_TOKEN", "true"),使配置管线完整贯通。

  3. _clean_output 按内容字符串匹配使架构脆弱 — 第 371 行直接匹配 opencode CLI 内部实现细节(如 "Asserting permissions""Adding reaction""opencode session ses_")。若 opencode CLI 更新日志前缀格式或国际化,这些模式会静默失效。建议优先按格式特征匹配(如第 368 行的时间戳正则模式)减少对具体内容的依赖。

New%20session%20-%202026-05-19T13%3A46%3A19.213Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

存在风险

安全分析摘要

PR 实现了 4 项改动,_clean_output() 已在第二版 commit (f9d6e5c) 中补全。代码以 subprocess 列表形式调用(无 shell=True),无命令注入风险。主要安全问题集中在信息泄露和 Token 权限范围。

阻塞项

阻塞项:无

建议项

  1. 错误输出未经脱敏直接写入 CI 日志 (run-multi-review.py:636-637) — reviewer 失败时前 500 字符输出通过 print(..., file=sys.stderr) 写入 Actions 日志。若 opencode CLI 错误消息中包含 API key 片段、内部路径或环境变量值,将直接暴露。建议对 output[:500] 做正则脱敏(如替换 sk-[a-zA-Z0-9]+ghp_[a-zA-Z0-9]+ 等常见密钥模式)。

  2. _clean_output() 非安全级 sanitizer (run-multi-review.py:359-383) — 函数定位为噪声过滤(ANSI codes、日志行、session 元数据),但不对评论内容做敏感信息检测。若 reviewer 输出中包含密钥、Token、内部路径等,仍会直接进入 PR 评论和 coordinator prompt。建议叠加一个秘密扫描层(如 re.sub 匹配常见密钥格式)再输出到 PR。

  3. USE_GITHUB_TOKEN = "true" 扩大 Token 作用域 (run-multi-review.py:198) — 原代码显式设为 "false" 阻止各 reviewer 子进程自主发 PR 评论。改为 "true" 后每个并行 opencode 实例均可使用 GITHUB_TOKEN 调用 GitHub API。若某个子进程被恶意 prompt 操控,理论上可通过 Token 发布评论、修改 Issue 等。建议评估是否可通过 GITHUB_TOKEN 权限降级(如只给 pull-requests: read)或 cleanup_error_comments() 之外的评论管控策略来缓解。

New%20session%20-%202026-05-19T13%3A46%3A56.811Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

可合并

综合审查意见

PR 实现了全部 4 项声称改动,_clean_output() 已在 commit f9d6e5c 中补全,前序 review 中指出的缺失问题已被解决。代码结构合理,SQLite 并行隔离和 run_script 存在性检查正确实现。无功能层面的阻塞项。

阻塞项:无

建议项:

  1. USE_GITHUB_TOKEN 硬编码覆盖用户配置(多 reviewer 确认: security, architecture)— _run_opencode() 第 198 行无条件设 "true",忽略 MULTI_REVIEW_USE_GITHUB_TOKEN 环境变量,action.ymluse-github-token 输入形同虚设。建议改为 env.get("USE_GITHUB_TOKEN", "true")

  2. 错误输出未脱敏直接写入 CI 日志(security)— reviewer 失败时前 500 字符输出直接打印到 stderr,可能暴露 API key 片段或内部路径。

  3. USE_GITHUB_TOKEN=true 副作用未评估(多 reviewer 确认: quality, security, architecture)— 原代码设 "false" 并注释 "prevent per-agent PR comments"。改为 "true" 后各 opencode 子进程可能向 PR 发表中间评论,cleanup_error_comments() 仅清理 error 模式评论。建议验证或添加管控机制。

  4. _clean_output() 缺少敏感信息脱敏(security)— 函数仅过滤 opencode 元数据行,未对 sk-[a-zA-Z0-9]+ghp_[a-zA-Z0-9]+ 等密钥模式做正则替换,若 reviewer 输出中意外包含此类信息会直接进入 PR 评论和 coordinator prompt。

  5. Prompt Injection 风险(security)— reviewer 输出经清洗后直接拼接进入 coordinator LLM prompt,攻击者可在 PR 代码中植入 prompt injection 文本,建议添加分隔符或内容转义。

  6. _clean_output() 按内容字符串匹配使架构脆弱(architecture)— 第 371 行直接匹配 opencode CLI 内部实现细节(如 "Asserting permissions""Adding reaction"),若 CLI 更新日志格式或国际化,模式会静默失效。建议优先按格式特征匹配。

  7. _run_reviewer_inner 中 Exception 捕获过宽(quality)— except Exception as e 捕获所有异常并 continue,可能掩盖 KeyErrorTypeError 等编程错误。

New%20session%20-%202026-05-19T13%3A47%3A45.396Z
opencode session  |  github run

@github-actions
Copy link
Copy Markdown

permission: admin
service: "session.prompt",
"session.id": "ses_1bf81ea8cffeNt6I1Ok049zNhS",
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",
}

可合并

综合审查意见

PR 实现了全部 4 项声称改动,_clean_output() 已在 commit f9d6e5c 中补全,前序 review 中指出的缺失问题已被解决。代码结构合理,SQLite 并行隔离和 run_script 存在性检查正确实现。无功能层面的阻塞项。

阻塞项:无

建议项:

  1. USE_GITHUB_TOKEN 硬编码覆盖用户配置(多 reviewer 确认: security, architecture)— _run_opencode() 第 198 行无条件设 "true",忽略 MULTI_REVIEW_USE_GITHUB_TOKEN 环境变量,action.ymluse-github-token 输入形同虚设。建议改为 env.get("USE_GITHUB_TOKEN", "true")

  2. 错误输出未脱敏直接写入 CI 日志(security)— reviewer 失败时前 500 字符输出直接打印到 stderr,可能暴露 API key 片段或内部路径。

  3. USE_GITHUB_TOKEN=true 副作用未评估(多 reviewer 确认: quality, security, architecture)— 原代码设 "false" 并注释 "prevent per-agent PR comments"。改为 "true" 后各 opencode 子进程可能向 PR 发表中间评论,cleanup_error_comments() 仅清理 error 模式评论。建议验证或添加管控机制。

  4. _clean_output() 缺少敏感信息脱敏(security)— 函数仅过滤 opencode 元数据行,未对 sk-[a-zA-Z0-9]+ghp_[a-zA-Z0-9]+ 等密钥模式做正则替换,若 reviewer 输出中意外包含此类信息会直接进入 PR 评论和 coordinator prompt。

  5. Prompt Injection 风险(security)— reviewer 输出经清洗后直接拼接进入 coordinator LLM prompt,攻击者可在 PR 代码中植入 prompt injection 文本,建议添加分隔符或内容转义。

  6. _clean_output() 按内容字符串匹配使架构脆弱(architecture)— 第 371 行直接匹配 opencode CLI 内部实现细节(如 "Asserting permissions""Adding reaction"),若 CLI 更新日志格式或国际化,模式会静默失效。建议优先按格式特征匹配。

  7. _run_reviewer_inner 中 Exception 捕获过宽(quality)— except Exception as e 捕获所有异常并 continue,可能掩盖 KeyErrorTypeError 等编程错误。

service: "session.prompt",
"session.id": "ses_1bf81ea8cffeNt6I1Ok049zNhS",
step: 1,
}
service: "session.prompt",
"session.id": "ses_1bf81ea8cffeNt6I1Ok049zNhS",
}


详细审查报告:

⚠️ quality

permission: admin
Command failed with code 128: git fetch origin --depth=20 fix/multi-review-ci
fatal: Unable to create '/home/runner/work/opencode-actions/opencode-actions/.git/shallow.lock': File exists.

Another git process seems to be running in this repository, or the lock file may be stale
::error::fatal: Unable to create '/home/runner/work/opencode-actions/opencode-actions/.git/shallow.lock': File exists.%0A%0AAnother git process seems to be running in this repository, or the lock file may be stale%0A

✅ security

permission: admin
service: "session.prompt",
"session.id": "ses_1bf8427acffeOMg8XRWg8zL32s",
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_1bf8427acffeOMg8XRWg8zL32s",
step: 1,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

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

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

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

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

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

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

存在风险

安全分析摘要:本次 PR 包含了 _clean_output() 函数(注:已在最新 commit f9d6e5c 中补全,并非之前 reviewer 所说的缺失)、USE_GITHUB_TOKEN 改为 true、并行 XDG_CACHE_HOME 隔离、run_script 存在性检查。没有 SQL 注入、命令注入或硬编码凭据等严重漏洞,但存在若干安全改进点。

阻塞项:无

建议项:

  1. USE_GITHUB_TOKEN 硬编码覆盖用户配置(第 198 行)— _run_opencode() 无条件设为 "true",即使 action 的 MULTI_REVIEW_USE_GITHUB_TOKEN=false 也无法阻止各 opencode 子进程使用 GITHUB_TOKEN 自主发布 PR 评论(原设计意图即为禁用此行为)。建议改为从 os.environ.get("USE_GITHUB_TOKEN", "true") 读取,尊重用户配置。

  2. 错误输出直接打印到 stderr,可能泄露敏感信息(第 638、323 行)— reviewer/coordinator 失败时将前 500 字符原始输出写入 GitHub Actions 日志。若 opencode CLI 错误信息中包含 API key 片段、环境变量值或内部路径,将直接暴露在日志中。

  3. _clean_output() 黑名单方式不完整,缺少敏感信息脱敏(第 359-383 行)— 函数仅按固定字符串列表过滤 opencode 元数据行,未对 API key、Token 等敏感模式做正则脱敏(如 sk-[a-zA-Z0-9]+ghp_[a-zA-Z0-9]+)。若 opencode CLI 输出中意外包含此类信息,会直接写入 PR 评论区。

  4. Prompt injection 风险(第 310-317 行)— reviewer 输出经 _clean_output() 过滤后直接拼接进入 coordinator 的 LLM prompt。攻击者可在 PR 代码中植入 prompt injection 文本,影响 coordinator 的合成输出。虽然 read-only 设计下影响范围有限,仍值得关注(添加分隔符或内容转义)。

  5. _clean_output() ANSI 转义正则不够完整(第 362 行)— \x1b\[[0-9;]*m 仅匹配 SGR 类序列,不匹配 \x1b[?25l(光标隐藏)、\x1b]8;;...\x1b\(OSC-8 超链接)等其他 ANSI 序列。建议改用社区成熟的 ansi-markup 或正则表达式覆盖更多模式。

service: "session.prompt",
"session.id": "ses_1bf8427acffeOMg8XRWg8zL32s",
step: 8,
}
service: "session.prompt",
"session.id": "ses_1bf8427acffeOMg8XRWg8zL32s",
}
service: "session.prompt",
"session.id": "ses_1bf8427acffeOMg8XRWg8zL32s",
step: 0,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}
存在风险,5项改进建议,无阻塞项

service: "session.prompt",
"session.id": "ses_1bf8427acffeOMg8XRWg8zL32s",
step: 1,
}
service: "session.prompt",
"session.id": "ses_1bf8427acffeOMg8XRWg8zL32s",
}
Command failed with code 128: git push
remote: Permission to sun-praise/opencode-actions.git denied to github-actions[bot].
fatal: unable to access 'https://github.com/sun-praise/opencode-actions/': The requested URL returned error: 403
::error::remote: Permission to sun-praise/opencode-actions.git denied to github-actions[bot].%0Afatal: unable to access 'https://github.com/sun-praise/opencode-actions/': The requested URL returned error: 403%0A
OpenCode attempt 1/3 failed, retrying in 15s...
permission: admin
service: "session.prompt",
"session.id": "ses_1bf82a854ffeY7P1zIzHbtOvTh",
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_1bf82a854ffeY7P1zIzHbtOvTh",
step: 1,
}
"llm.runtime": "ai-sdk",
"llm.provider": "deepseek",
"llm.model": "deepseek-v4-flash",
}

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

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

存在风险

安全分析摘要

PR 实现了 4 项改动,_clean_output() 已在第二版 commit (f9d6e5c) 中补全。代码以 subprocess 列表形式调用(无 shell=True),无命令注入风险。主要安全问题集中在信息泄露和 Token 权限范围。

阻塞项

阻塞项:无

建议项

  1. 错误输出未经脱敏直接写入 CI 日志 (run-multi-review.py:636-637) — reviewer 失败时前 500 字符输出通过 print(..., file=sys.stderr) 写入 Actions 日志。若 opencode CLI 错误消息中包含 API key 片段、内部路径或环境变量值,将直接暴露。建议对 output[:500] 做正则脱敏(如替换 sk-[a-zA-Z0-9]+ghp_[a-zA-Z0-9]+ 等常见密钥模式)。

  2. _clean_output() 非安全级 sanitizer (run-multi-review.py:359-383) — 函数定位为噪声过滤(ANSI codes、日志行、session 元数据),但不对评论内容做敏感信息检测。若 reviewer 输出中包含密钥、Token、内部路径等,仍会直接进入 PR 评论和 coordinator prompt。建议叠加一个秘密扫描层(如 re.sub 匹配常见密钥格式)再输出到 PR。

  3. USE_GITHUB_TOKEN = "true" 扩大 Token 作用域 (run-multi-review.py:198) — 原代码显式设为 "false" 阻止各 reviewer 子进程自主发 PR 评论。改为 "true" 后每个并行 opencode 实例均可使用 GITHUB_TOKEN 调用 GitHub API。若某个子进程被恶意 prompt 操控,理论上可通过 Token 发布评论、修改 Issue 等。建议评估是否可通过 GITHUB_TOKEN 权限降级(如只给 pull-requests: read)或 cleanup_error_comments() 之外的评论管控策略来缓解。

service: "session.prompt",
"session.id": "ses_1bf82a854ffeY7P1zIzHbtOvTh",
step: 4,
}
service: "session.prompt",
"session.id": "ses_1bf82a854ffeY7P1zIzHbtOvTh",
}

@Svtter Svtter merged commit 7499704 into main May 19, 2026
5 checks passed
Svtter added a commit that referenced this pull request May 19, 2026
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