-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(review): stop the agent transcript from executing workflow commands #8683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1133,6 +1133,23 @@ jobs: | |
| fi | ||
| export QWEN_REVIEW_DEADLINE_RESERVE_SECONDS | ||
| set +e | ||
| # The agent streams its ENTIRE transcript to stdout, and the runner | ||
| # scans every line for workflow commands. A tool result that quotes | ||
| # a file containing one is executed as a command: reviewing a PR | ||
| # that touches `actions/setup-node`, the agent read that action's | ||
| # own main.ts, which legitimately contains | ||
| # `core.info(\`##[add-matcher]${...}\`)`. The runner took the rest | ||
| # of the JSON line as a matcher path and errored. Observed on run | ||
| # 31167034020 (PR #8681): three `Unable to process command`, and | ||
| # 1h37m of review work discarded. Any PR whose review quotes a file | ||
| # containing `##[...]` or `::...::` breaks the same way — this | ||
| # repository's own workflows included. | ||
| # Turn command parsing off around the agent and nothing else. The | ||
| # token is random per attempt, so no output the agent produces can | ||
| # guess it and re-enable parsing early. | ||
| local stop_token | ||
| stop_token="qwen-review-stop-$(date +%s%N)-${RANDOM}${RANDOM}" | ||
| echo "::stop-commands::${stop_token}" | ||
| # GNU timeout times out command children unless --foreground is used. | ||
| timeout --kill-after=10s "${attempt_timeout}s" qwen \ | ||
| --auth-type openai \ | ||
|
|
@@ -1142,6 +1159,18 @@ jobs: | |
| --output-format stream-json \ | ||
| | tee "$LOG_PATH" | ||
| local ps=("${PIPESTATUS[@]}") | ||
| # Resume BEFORE anything else can exit: errexit is still off here, | ||
| # so this line is reached on every agent outcome — timeout, crash | ||
| # or success. Leaving it off would silently swallow this job's own | ||
| # ::error:: and the fallback comment's diagnostics for the rest of | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] Both specifics in this justification are wrong, while the real justification is stronger and sits one screen below.
The one thing a missing resume actually loses is The code is correct as-is — only the stated reason needs narrowing to the retry Raised in the round-1 self-review; still on head. 中文说明这段理由里的两个具体说法都不成立,而真正的理由更有力、就在下方一屏处。
resume 缺失真正会丢掉的,是 1281 行的 代码本身没问题,只需把理由收窄到重试的 |
||
| # the run, turning one broken review into a silent one. | ||
| # Lead with a newline: the runner only recognises `::cmd::` at the | ||
| # start of a line, and `--kill-after` SIGKILLs the agent, which can | ||
| # leave a partial stream-json line with no trailing newline. An | ||
| # `echo` would append the resume to that fragment, where it is just | ||
| # text — parsing would stay off for the rest of the job, on exactly | ||
| # the path this guard exists to survive. | ||
| printf '\n::%s::\n' "$stop_token" | ||
| set -e | ||
| local qwen_status="${ps[0]}" | ||
| local tee_status="${ps[1]}" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Suggestion] The
::...::half of this claim is not reachable, and stating it here misdescribes the threat model the guard defends against.Verified against
actions/runner@main,src/Runner.Common/ActionCommand.cs:TryParseV2(::cmd::…) —message.TrimStart()thenStartsWith("::"): line start only.TryParse(##[cmd]…) —message.IndexOf("##["): anywhere in the line.Under
--output-format stream-jsonevery transcript line starts with{, and JSON escapes newlines as\n, so quoted file content can never place a::…::at a line start. Only the##[…]form is reachable from a quoted file — which is exactly why the incident fired from inside a JSON line, and exactly why the resume below needs its leading newline. As written, the comment implies the two forms are symmetric; a future reader could reasonably conclude the leading-newlineprintfis interchangeable with anecho.Suggest narrowing to the
##[...]form (the::...::asymmetry is worth one clause, since it is what makes the resume fragile).Raised in the round-1 self-review; still on head.
中文说明
这句里的
::...::一半不可达,写在这里会误述该守卫真正防的威胁模型。对照
actions/runner@main的src/Runner.Common/ActionCommand.cs核实:TryParseV2(::cmd::…)先TrimStart()再StartsWith("::"),仅行首匹配;TryParse(##[cmd]…)用IndexOf("##["),行内任意位置匹配。在
--output-format stream-json下每行会话都以{开头,且 JSON 把换行转义成\n,因此被引用的文件内容永远无法把::…::放到行首。能从引用文件触发的只有##[…]形式——这正是事故从 JSON 行内部触发的原因,也正是下面 resume 必须以换行开头的原因。按现在的写法,注释暗示两种形式对称,后来的读者可能因此认为那个带前导换行的printf与echo可以互换。建议收窄为只讲
##[...]形式(::...::的不对称性值得单独一句,因为它才是 resume 脆弱的根源)。第一轮自审已提出,head 上仍未修改。