Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/.size-baseline
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
6495 pr-self-report-label.yml
9646 qwen-autofix-fork-bridge.yml
5942 qwen-autofix-fork-signal.yml
426924 qwen-autofix.yml
432155 qwen-autofix.yml
7061 qwen-ci-flaky-rerun.yml
165876 qwen-code-pr-review.yml
197929 qwen-code-pr-review.yml
79041 qwen-fleet-shepherd.yml
20525 qwen-issue-followup-bot.yml
5760 qwen-pr-safety-precheck.yml
Expand Down
53 changes: 50 additions & 3 deletions .github/workflows/qwen-autofix.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ task-oriented guides — what a maintainer types and what happens next — see:
- [148. route — Persistent pool, not hosted: a hosted backlog queued route past the cron period, and af-005's…](#af-148)
- [149. review-address · Post autofix status comment — Round heartbeat: the announcement freezes at "working" for the whole round…](#af-149)
- [150. review-address · Post autofix status comment — Deep-link "Watch live progress" to THIS matrix leg's live log, not just the run…](#af-150)
- [151. review-address · Report dry-run / failure — Hold the stale-base refresh while a review-pr is in flight on the PR.…](#af-151)

---

Expand Down Expand Up @@ -2767,10 +2768,14 @@ In `review-scan` · `Scan for PRs with new feedback`.
```text
Delay-window fallback: a review run parked BEFORE its job
starts (the 10-minute environment wait) has no review-pr
check-run yet, but a push now would still cancel it via
synchronize. Only pull_request_target runs are cancelable —
check-run yet, but a push now would still supersede it (the
review workflow's own supersede handling, #10110: a parked or
pre-threshold run yields to the push — its work is discarded
exactly as the old synchronize cancel did — and only a
past-threshold run salvages; the hold keeps the loop from
forcing either). Only pull_request_target runs are affected —
comment/review-triggered runs use per-run concurrency groups
that a synchronize never cancels, so holding the round for
that a synchronize never touches, so holding the round for
one would defer autofix for nothing (R2-1). Match against the
scan's REVIEW_RUNS_JSON fetch — one page of the review
workflow's runs, empty on lookup failure — by immutable head
Expand Down Expand Up @@ -3989,3 +3994,45 @@ attempts), and one less thing to re-resolve on the
crashed-agent paths where this step's outputs may be all
that survived.
```

<a id="af-151"></a>

### 151. review-address · Report dry-run / failure — Hold the stale-base refresh while a review-pr is in flight on the PR.

In `review-address` · `Report dry-run / failure`.

```text
The scan's dispatch gate (#8888/#8899) already refuses to start a round
while review-pr is live, but the loop had one more head-moving write
outside that hold: this step's stale-base retry calls update-branch at
REPORT time, hours after the dispatch gate last looked. A review can
start in that window — a human /review comment, a bot re-request, or a
run the scan's fail-open probe missed — and the merge push would then
supersede a lifecycle review run mid-flight (#10110; before the salvage
threshold that discards its work exactly as the old cancel did), or
invalidate a command run's posting: every review pins the head it
reviews (QWEN_CI_REVIEW_EXPECTED_HEAD_SHA) and its guard blocks the
final post when the head moved, so even the uncancellable per-run-group
reviews lose their whole run to a head move.

So the retry probes for a live review first, with the scan gate's exact
probe pair: the statusCheckRollup filter (any live review-pr check from
the review workflow, trigger-independent), then the runs-API fallback
for pull_request_target runs still parked in the 10-minute delay window
with no check-run yet. On a live review the update is DEFERRED, not
skipped: the same 9999 sentinel MARK_TS the retry branch uses keeps the
feedback live, the next scan re-runs the round (itself held while the
review is still in flight), and that round's report step performs the
refresh once the review has landed. One extra round of latency, bounded
by MAX_ROUNDS, against hours of discarded review work.

Fail-open on probe errors, deliberately: the probe is an optimization,
and failing closed would wedge stale-base recovery — the path that
un-sticks red PRs — on any transient API error. A probe error therefore
reads as "no review live" and the update proceeds, which is exactly the
pre-#10110 behavior. The deferred headline joins CONSEC_FAIL's
streak-reset needles ("deferred a stale-base refresh"): like the
updated-a-stale-base round it defers to, the round's failure is not
evidence about the PR, and counting it toward the cap would park a PR
for having been reviewed at the wrong moment.
```
49 changes: 42 additions & 7 deletions .github/workflows/qwen-autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2631,8 +2631,9 @@ jobs:
if [[ "${REVIEW_PR_LIVE}" != "true" && -n "${REVIEW_WF_ID}" && -n "${PR_HEAD_OID}" ]]; then
# Delay-window fallback: a review run parked BEFORE its job
# starts (the 10-minute environment wait) has no review-pr
# check-run yet, but a push now would still cancel it via
# synchronize.
# check-run yet, but a push now would still supersede it
# (#10110: a parked or pre-threshold run yields to the push —
# its work is discarded exactly as a cancel did).
# Full rationale → qwen-autofix.md#af-099
REVIEW_RUN_STARTED_AT="$(jq -r --arg wf "${REVIEW_WF_ID}" --arg pr "${PR}" --arg head "${PR_HEAD_OID}" '
[ .workflow_runs[]?
Expand Down Expand Up @@ -5958,13 +5959,40 @@ jobs:
# generated checks complete after the conflict marker this
# same report posts — waking the very park it establishes.
# Full rationale → qwen-autofix.md#af-143
STALE_BASE_DEFERRED=false
if [[ "${AUDIT_VERDICT:-}" != 'conflict' ]]; then
MAIN_HEAD_R="$(gh api "repos/${REPO}/commits/${DEFAULT_BRANCH:-main}" --jq '.sha' 2> /dev/null || echo '')"
if [[ -n "${MAIN_HEAD_R}" && -n "${REPORT_HEAD}" ]]; then
CMP_R="$(gh api "repos/${REPO}/compare/${MAIN_HEAD_R}...${REPORT_HEAD}" --jq '.status' 2> /dev/null || echo '')"
if [[ "${CMP_R}" == 'behind' || "${CMP_R}" == 'diverged' ]] \
&& gh api -X PUT "repos/${REPO}/pulls/${PR}/update-branch" -f expected_head_sha="${REPORT_HEAD}" > /dev/null 2>&1; then
STALE_BASE_RETRY=true
if [[ "${CMP_R}" == 'behind' || "${CMP_R}" == 'diverged' ]]; then
# Review-in-flight hold for the loop's OWN report-time
# head move (#10110): same probe pair as the scan's
# dispatch gate (#8888), fail-open on probe errors.
# Full rationale → qwen-autofix.md#af-151
ROLLUP_R="$(gh pr view "${PR}" --repo "${REPO}" --json statusCheckRollup --jq '.statusCheckRollup // []' 2> /dev/null || echo '[]')"
REVIEW_LIVE_R="$(jq -r '
[ .[]
| select((((.status // .state // "") | IN("QUEUED", "IN_PROGRESS", "PENDING", "WAITING", "REQUESTED")) and ((.name // "") == "review-pr") and ((.workflowName // "") == "🧐 Qwen Pull Request Review"))) ]
| length > 0
' <<< "${ROLLUP_R}" 2> /dev/null || echo '')"
if [[ "${REVIEW_LIVE_R}" != 'true' ]]; then
RWID_R="$(gh api "repos/${REPO}/actions/workflows/qwen-code-pr-review.yml" --jq '.id' 2> /dev/null || echo '')"
LIVE_HEAD_R="$(jq -r '.headRefOid // ""' <<< "$(gh pr view "${PR}" --repo "${REPO}" --json headRefOid 2> /dev/null || echo '{}')")"
if [[ -n "${RWID_R}" ]]; then
REVIEW_LIVE_R="$(gh api "repos/${REPO}/actions/workflows/${RWID_R}/runs?per_page=100" 2> /dev/null | jq -r --arg pr "${PR}" --arg head "${LIVE_HEAD_R}" '
[ .workflow_runs[]?
| select((.event // "") == "pull_request_target")
| select((.status // "") | IN("queued", "waiting", "pending", "requested", "in_progress"))
| select((($head != "") and ((.head_sha // "") == $head)) or any(.pull_requests[]?; (.number | tostring) == $pr)) ]
| length > 0
' 2> /dev/null || echo '')"
fi
fi
if [[ "${REVIEW_LIVE_R}" == 'true' ]]; then
STALE_BASE_DEFERRED=true
elif gh api -X PUT "repos/${REPO}/pulls/${PR}/update-branch" -f expected_head_sha="${REPORT_HEAD}" > /dev/null 2>&1; then
STALE_BASE_RETRY=true
fi
fi
fi
fi
Expand All @@ -5976,6 +6004,13 @@ jobs:
MARK_TS='9999-12-31T23:59:59Z'
HEADLINE="🤖 AutoFix updated a stale base — the fix did not pass verification, but this PR was behind \`${DEFAULT_BRANCH:-main}\`, so it merged current main in via update-branch and will retry on the next scan. A stale base (a dependency or symbol main already changed) can fail the build without being the fix's fault; if it still fails once current, it hands off to a human."
HEADLINE_ZH="🤖 AutoFix 更新了一个过期的 base —— 修复未通过验证,但本 PR 落后于 \`${DEFAULT_BRANCH:-main}\`,因此已通过 update-branch 合入当前 main,并将在下次扫描时重试。过期的 base(main 已改动的依赖或符号)可能让构建失败而并非修复本身的错;若 base 更新后仍然失败,将移交人工处理。"
elif [[ "${STALE_BASE_DEFERRED}" == 'true' ]]; then
# Same sentinel semantics as the retry branch: feedback stays
# live, the next round re-runs, and ITS report step performs
# the base refresh once the review has landed.
MARK_TS='9999-12-31T23:59:59Z'
HEADLINE="🤖 AutoFix deferred a stale-base refresh — the fix did not pass verification and this PR is behind \`${DEFAULT_BRANCH:-main}\`, but a review of this PR is still in flight, and merging main now would cancel it or invalidate its posting (#10110). The refresh and this item's retry move to the next scan."

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The deferred headline this diff adds says the avoided merge "would cancel it or invalidate its posting (#10110)" — the pre-#10110 framing, and false for exactly the case this PR exists for: a past-threshold lifecycle review salvages and posts its historical-head review, neither cancelled nor invalidated, and the claim directly contradicts the "⏳ Historical-head review" note the salvage path posts into the same comment stream. Three unchanged strings carry the same newly-stale wording: the hold log at :2652 ("holding this round so the push cannot cancel it (#8888)"), the user-facing ack-on-defer comment at :2683 ("a push now would cancel it and discard its work" / "现在推送会取消该 review 并丢弃其工作"), and qwen-autofix.md#af-028's present-tense claim that head mutations cancel "via qwen-code-pr-review.yml's cancel-in-progress" — which reads as making the #8888 gate pointless now that the cancel is gone. Post-merge, a push supersedes in-run: only a parked/pre-threshold run yields. The deferral behavior itself is correct; only the wording is wrong.

Suggested fix: reword all four to the new semantics, e.g. "holding this round so the push cannot supersede it (#8888/#10110)" and "merging main now would supersede it — an early run loses its work, a late run is forced to salvage mid-flight (#10110)", matching the updated af-099 text.

中文说明

[Suggestion] 本 diff 新增的延迟标题称被避免的合并"会取消它或使其发布失效(#10110)"——这是 #10110 之前的表述,而且对本 PR 要解决的核心场景恰好是错的:超阈值的生命周期评审会 salvage 并发布其历史 head 评审,既未被取消也未失效,该说法与 salvage 路径发布到同一评论流的"⏳ Historical-head review"说明直接矛盾。另有三处未改动的字符串带有同样新近过时的表述::2652 的 hold 日志("holding this round so the push cannot cancel it (#8888)")、:2683 面向用户的延迟确认评论("a push now would cancel it and discard its work" / "现在推送会取消该 review 并丢弃其工作"),以及 qwen-autofix.md#af-028 中"head 变更会通过 qwen-code-pr-review.yml 的 cancel-in-progress 取消在飞评审"的现在时表述——在取消已移除后读起来像是在说 #8888 门禁毫无意义。合并后,推送触发 run 内的取代决策:只有停放/未达阈值的 run 会让位。延迟行为本身是正确的;只是措辞错了。

建议修复:把四处全部改为新语义,例如"holding this round so the push cannot supersede it (#8888/#10110)"与"merging main now would supersede it — an early run loses its work, a late run is forced to salvage mid-flight (#10110)",与更新后的 af-099 文本一致。

— qwen3.8-max via Qwen Code /review (v0.22.0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred to the next round (this round's batch cap was ~8 findings; the Critical plus the salvage-logic correctness fixes and their witnesses went first). The wording finding is accepted — all four spots (the new deferred headline, the hold log at ~:2652, the ack-on-defer comment at ~:2683, and qwen-autofix.md#af-028) will be reworded to the post-#10110 supersede semantics together next round.

中文说明

推迟到下一轮(本轮批量上限约 8 项发现;Critical 加 salvage 逻辑的正确性修复及其见证优先)。措辞发现已接受 —— 四处(新增的延迟标题、约 :2652 的 hold 日志、约 :2683 的延迟确认评论、qwen-autofix.md#af-028)将在下一轮一并改为 #10110 之后的取代语义。

HEADLINE_ZH="🤖 AutoFix 暂缓了一次过期 base 刷新 —— 修复未通过验证且本 PR 落后于 \`${DEFAULT_BRANCH:-main}\`,但该 PR 上仍有一轮评审在运行,现在合入 main 会取消该评审或使其发布失效(#10110)。base 刷新与此项的重试推迟到下次扫描。"
else
# Say what actually happens next. The old "A human should
# take over this PR" read as a full release, but the loop
Expand Down Expand Up @@ -6032,7 +6067,7 @@ jobs:

# Consecutive-failure circuit breaker, distinct from the round cap.
# Full rationale → qwen-autofix.md#af-069
if [[ "${MARK_ROUND}" != "${MAX_ROUNDS}" ]] && [[ "${PREPARE_OUTCOME}" == 'success' || "${PREPARE_OUTCOME}" == 'failure' ]] && [[ "${STALE_BASE_RETRY:-false}" != 'true' ]] && { [[ -z "${API_ERROR_DETAIL}" ]] || [[ "${API_ERROR_KIND}" == 'auth' ]]; }; then
if [[ "${MARK_ROUND}" != "${MAX_ROUNDS}" ]] && [[ "${PREPARE_OUTCOME}" == 'success' || "${PREPARE_OUTCOME}" == 'failure' ]] && [[ "${STALE_BASE_RETRY:-false}" != 'true' ]] && [[ "${STALE_BASE_DEFERRED:-false}" != 'true' ]] && { [[ -z "${API_ERROR_DETAIL}" ]] || [[ "${API_ERROR_KIND}" == 'auth' ]]; }; then
CONSEC_FAIL=1
if [[ -f "${WORKDIR}/ic.json" ]]; then
COMMENTS_JSON="$(cat "${WORKDIR}/ic.json")"
Expand All @@ -6054,7 +6089,7 @@ jobs:
| (.body | gsub("\r"; "") | split("\n")[0])' <<< "${COMMENTS_JSON}" 2> /dev/null || true)"
while IFS= read -r H; do
[[ -n "${H}" ]] || continue
if [[ "${H}" == *"Addressed the latest review feedback"* || "${H}" == *"no changes needed"* || "${H}" == *"AutoFix could not start —"* || "${H}" == *"updated a stale base"* ]]; then
if [[ "${H}" == *"Addressed the latest review feedback"* || "${H}" == *"no changes needed"* || "${H}" == *"AutoFix could not start —"* || "${H}" == *"updated a stale base"* || "${H}" == *"deferred a stale-base refresh"* ]]; then
CONSEC_FAIL=1
else
CONSEC_FAIL=$(( CONSEC_FAIL + 1 ))
Expand Down
Loading
Loading