Skip to content
Merged
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
123 changes: 109 additions & 14 deletions .github/workflows/qwen-autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,8 @@ jobs:
# applies TAKEOVER_LABEL, 'TAKEOVER_COMMAND stop' removes it —
# nothing else. The label stays the single source of truth:
# engagement and release happen ONLY via the label events
# below, so the command's whole blast radius is one label
# toggle. Exact match on the trimmed body (constants, never
# below; the command also posts acks directly in both
# directions (#7999, #8002). Exact match on the trimmed body (constants, never
# user-input parsing); allowed senders: the PR author (who may
# lack label access) or a write+ collaborator. This immediately
# narrows a previously fully-closed surface reopened under
Expand Down Expand Up @@ -518,7 +518,19 @@ jobs:
else
DO_REVIEW=true
ROUTE_PR="$(sanitize_number "${PR_NUMBER_EVENT}")"
TAKEOVER_ACK='engaged'
if [[ "${SENDER_LOGIN}" == "${AUTOFIX_BOT}" ]]; then
# The bot only applies this label from takeover-command,
# which posts the engage ack ITSELF: the labeled event
# has been observed to simply not fire (#7999 — the
# author read the silence as failure and removed the
# label; #8002), so the user-visible ack must not
# depend on this round-trip. Suppress only the ack —
# the immediate scan is this event's real work and
# still routes.
echo "🧭 engage ack skipped: label applied by ${AUTOFIX_BOT} — the command path already acked"
else
TAKEOVER_ACK='engaged'
fi
echo "🧭 ${TAKEOVER_LABEL} applied by ${SENDER_LOGIN} on PR #${PR_NUMBER_EVENT} → review phase (takeover)"
fi
elif [[ "${EVENT_ACTION}" == 'unlabeled' ]]; then
Expand All @@ -533,6 +545,12 @@ jobs:
# fail its identity check — a red run for a label that
# never engaged anything. Log and stop.
echo "🧭 takeover release ignored: PR is a fork (${PR_HEAD_REPO} != ${REPO})"
elif [[ "${SENDER_LOGIN}" == "${AUTOFIX_BOT}" ]]; then
# Mirror of the labeled-path suppression: the bot only
# removes this label from takeover-command, which posts
# the release ack itself — acking here too would
# double-post on every command-driven stop.
echo "🧭 release ack skipped: label removed by ${AUTOFIX_BOT} — the command path already acked"
else
TAKEOVER_ACK='released'
echo "🧭 ${TAKEOVER_LABEL} removed from PR #${PR_NUMBER_EVENT} by ${SENDER_LOGIN} → released"
Expand Down Expand Up @@ -1387,10 +1405,27 @@ jobs:
# State/base can change while this job sits in its per-PR queue —
# re-verify what the route checked so a stale command cannot label
# a closed or non-main PR.
if [[ "$(jq -r '.state // ""' <<< "${PR_INFO}")" != "OPEN" || "$(jq -r '.baseRefName // ""' <<< "${PR_INFO}")" != "main" ]]; then
echo "🧭 takeover command dropped: PR #${PR} is no longer an open main-targeting PR"
if [[ "$(jq -r '.state // ""' <<< "${PR_INFO}")" != "OPEN" ]]; then
echo "🧭 takeover command dropped: PR #${PR} is no longer an open PR"
exit 0
fi
CMD_BASE_REF="$(jq -r '.baseRefName // ""' <<< "${PR_INFO}")"
if [[ "${CMD_BASE_REF}" != "main" ]]; then
if [[ "${CMD}" == 'add' ]]; then
# Refuse OUT LOUD — the silent drop made a /takeover on a
# stacked PR indistinguishable from a lost event. Mirrors the
# label path's base-refused ack, except no label was applied
# here, so the ask is to re-run the command after retargeting
# (not "the label is left in place").
gh pr comment "${PR}" --repo "${REPO}" --body "$(printf '🚫 Takeover not engaged: the loop only manages PRs that target `main`, and this one targets `%s`. A stacked PR moves whenever its base branch does, so "new feedback since the last round" and base-conflict resolution are not well defined until the base lands. Retarget this PR to `main` once the base PR merges and re-run `%s` — or take over the base PR instead.\n\n<details>\n<summary>中文说明</summary>\n\n🚫 未接管:循环只管理以 `main` 为 base 的 PR,而本 PR 的 base 是 `%s`。堆叠 PR 会随 base 分支移动,因此“自上一轮以来的新反馈”与 base 冲突处理都无法良定义。待 base 的 PR 合入后将本 PR 改为面向 `main` 并重新执行 `%s`;或改为接管 base 那个 PR。\n\n</details>\n\n<!-- takeover-ack base-refused -->' "${CMD_BASE_REF}" "${TAKEOVER_COMMAND}" "${CMD_BASE_REF}" "${TAKEOVER_COMMAND}")"
echo "🧭 takeover command refused: PR #${PR} targets '${CMD_BASE_REF}' not 'main'"
exit 0
fi
# 'stop' proceeds: removing the label from a non-main PR is
# harmless and matches the latest intent — dropping it here left
# a manually-applied label stuck with no command able to remove
# it (the label path's release ack ignores non-main PRs too).
fi
# Skip wins over takeover EVERYWHERE — including here: engaging or
# re-arming a skip-labeled PR would post an 'engaged' window anchor
# for management that the scans deliberately refuse to perform.
Expand Down Expand Up @@ -1443,20 +1478,64 @@ jobs:
else
gh pr edit "${PR}" --repo "${REPO}" --add-label "${TAKEOVER_LABEL}"
echo "🏷️ applied ${TAKEOVER_LABEL} to #${PR}"
# Ack HERE, not via the pull_request:labeled round-trip: that
# event has been observed to simply not fire (#7999 — the
# author read the silence as failure and removed the label;
# #8002 — no ack for hours), and fork label events could never
# ack at all (they carry no secrets). Every admission gate
# above has already passed, so 'engaged' is truthful for both
# in-repo and fork PRs. The route side suppresses the
# label-path ack when the label sender is the bot, and the
# scan's first-pickup ack dedups against this comment — and
# heals it on the next scan if this post fails, which is why
# a failure here only warns.
FORK_NOTE=''
FORK_NOTE_ZH=''
if [[ "$(jq -r 'if has("isCrossRepository") then .isCrossRepository else true end' <<< "${PR_INFO}")" != "false" ]]; then
FORK_NOTE=' This is a fork PR, so the first round comes from the next scheduled scan (usually within minutes).'
FORK_NOTE_ZH='本 PR 来自 fork,首轮处理将由下一次定时扫描执行(通常几分钟内)。'
fi
gh pr comment "${PR}" --repo "${REPO}" --body "$(printf '🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached.%s Remove the `%s` label (or comment `%s stop`) to release.\n\n<details>\n<summary>中文说明</summary>\n\n🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。%s移除 `%s` 标签(或评论 `%s stop`)即可释放。\n\n</details>\n\n<!-- takeover-ack engaged -->' "${FORK_NOTE}" "${TAKEOVER_LABEL}" "${TAKEOVER_COMMAND}" "${FORK_NOTE_ZH}" "${TAKEOVER_LABEL}" "${TAKEOVER_COMMAND}")" \
|| echo "::warning::engage ack comment failed on #${PR}; the scan's first-pickup ack heals it"
fi
else
if [[ "${HAS}" != 'true' ]]; then
echo "ℹ️ #${PR} does not carry ${TAKEOVER_LABEL} — nothing to do"
else
gh pr edit "${PR}" --repo "${REPO}" --remove-label "${TAKEOVER_LABEL}"
echo "🏷️ removed ${TAKEOVER_LABEL} from #${PR}"
# Release ack, direct from the command — the exact mirror of
# the engage side above, for the same reason: the unlabeled
# round-trip is the thing we no longer trust, fork unlabeled
# events can never ack (no secrets), and a non-main release
# never even reaches the ack job. A loud add next to a mute
# stop would re-create the "did it work or did the event get
# lost?" ambiguity on the release side. Variant selection
# mirrors the ack job verbatim (live author + skip label from
# the same PR_INFO the gates used); the route side suppresses
# the unlabeled-path ack when the label sender is the bot.
REL_AUTHOR="$(jq -r '.author.login // ""' <<< "${PR_INFO}")"
REL_HAS_SKIP="$(jq -r --arg t "${SKIP_LABEL}" '[.labels[].name] | index($t) != null' <<< "${PR_INFO}")"
Comment thread
qwen-code-dev-bot marked this conversation as resolved.
if [[ "${REL_AUTHOR}" == "${AUTOFIX_BOT}" && "${REL_HAS_SKIP}" == "true" ]]; then
REL_BODY="$(printf '👋 Takeover mode ended. This bot-authored PR also carries `%s`, which opts it out of standard bot management entirely — nothing will engage it until that label is removed.\n\n<details>\n<summary>中文说明</summary>\n\n👋 接管模式结束。本 bot 创建的 PR 同时带有 `%s`,已完全退出常规 bot 管理 —— 移除该标签前不会有任何介入。\n\n</details>\n\n<!-- takeover-ack released -->' "${SKIP_LABEL}" "${SKIP_LABEL}")"
elif [[ "${REL_AUTHOR}" == "${AUTOFIX_BOT}" ]]; then
REL_BODY="$(printf '👋 Takeover mode ended: the raised round cap no longer applies. This is a bot-authored PR, so STANDARD bot management continues under the strict cap (apply `%s` to opt it out entirely). Re-apply `%s` (or comment `%s`) for the raised cap again.\n\n<details>\n<summary>中文说明</summary>\n\n👋 接管模式结束:提升的轮次上限不再适用。这是 bot 创建的 PR,常规 bot 管理仍将继续(严格上限;如需完全退出请打 `%s`)。重新打上 `%s` 标签(或评论 `%s`)可恢复提升上限。\n\n</details>\n\n<!-- takeover-ack released -->' "${SKIP_LABEL}" "${TAKEOVER_LABEL}" "${TAKEOVER_COMMAND}" "${SKIP_LABEL}" "${TAKEOVER_LABEL}" "${TAKEOVER_COMMAND}")"
else
REL_BODY="$(printf '👋 Takeover released: the autofix loop will no longer engage this PR (an in-flight round, if any, completes its bounded work). Re-apply `%s` (or comment `%s`) to re-engage.\n\n<details>\n<summary>中文说明</summary>\n\n👋 已释放:autofix 循环不再介入此 PR(在飞的一轮如有,将完成其有界工作)。重新打上 `%s` 标签(或评论 `%s`)即可再次接管。\n\n</details>\n\n<!-- takeover-ack released -->' "${TAKEOVER_LABEL}" "${TAKEOVER_COMMAND}" "${TAKEOVER_LABEL}" "${TAKEOVER_COMMAND}")"
fi
gh pr comment "${PR}" --repo "${REPO}" --body "${REL_BODY}" \
|| echo "::warning::release ack comment failed on #${PR}"
fi
fi

# ===========================================================================
# TAKEOVER ACK — visible confirmation when a maintainer engages or releases
# a PR via the takeover label. Label events are explicit user actions, so
# every toggle acks (no dedup wanted). In-repo PRs only reach this job.
# a PR via the takeover label. Manual label toggles are explicit user
# actions, so every one acks (no dedup wanted). Command-driven toggles are
# acked by takeover-command itself in BOTH directions — the label event has
# been observed to not fire at all (#7999, #8002), so those acks cannot
# depend on this round-trip — and the route suppresses this job for them
# (label sender is the bot). In-repo PRs only reach this job.
# ===========================================================================
# Re-arm a stranded PR without deleting anything. Recovery previously meant
# `gh api -X DELETE` on the bot's own autofix-eval marker comment: raw API
Expand Down Expand Up @@ -1984,13 +2063,29 @@ jobs:
| .created_at] | sort | last // ""' "${WORKDIR}/pr-events.json")"
if [[ -z "${LAST_ENGAGE_ACK_TS}" ]]; then
NEED_ENGAGE_ACK='true'
# In-repo label events have a DEDICATED ack job — the scan is
# only its healer. Within a short grace after the label lands,
# defer: a concurrent ack job must not be double-posted (that
# shifts the window anchor). A failed ack job is healed by the
# next scan, which is past the grace. Forks have no ack job,
# so no grace applies there.
if [[ "$(jq -r '.isCrossRepository // false' <<< "${PR_META}")" != "true" ]] \
# Grace windows keyed by WHO owns the missing ack, read from
# the label event's actor (pr-events.json is already here).
# A bot-applied label came from takeover-command, which posts
# the ack itself within seconds — fork or in-repo alike — so
# a SHORT grace covers the write's own latency and an
# ic.json snapshot taken between the label write and the ack
# landing; past it, the command's post failed and the next
# scheduled scan heals it (≤10 min), instead of waiting on
# a label event that may never arrive. A human-applied
# in-repo label is owned by the
# DEDICATED ack job, which needs job-spin-up time — the
# longer grace stands. A human-labeled fork has no other
# owner, so no grace: the scan posts right here.
LAST_LABELED_BY="$(jq -rs --arg lb "${TAKEOVER_LABEL}" '
add | [.[] | select(.event == "labeled")
| select((.label.name // "") == $lb)]
| sort_by(.created_at) | last | .actor.login // ""' "${WORKDIR}/pr-events.json")"
if [[ "${LAST_LABELED_BY}" == "${AUTOFIX_BOT}" ]]; then
if [[ -n "${LAST_LABELED_TS}" && "${LAST_LABELED_TS}" > "$(date -u -d '45 seconds ago' +%Y-%m-%dT%H:%M:%SZ)" ]]; then
echo "🧭 engage ack deferred for #${PR}: command-applied label <45s ago — the command's own ack is in flight"
NEED_ENGAGE_ACK='false'
fi
elif [[ "$(jq -r '.isCrossRepository // false' <<< "${PR_META}")" != "true" ]] \
&& [[ -n "${LAST_LABELED_TS}" && "${LAST_LABELED_TS}" > "$(date -u -d '3 minutes ago' +%Y-%m-%dT%H:%M:%SZ)" ]]; then
echo "🧭 engage ack deferred for #${PR}: in-repo label applied <3m ago — the ack job owns it"
NEED_ENGAGE_ACK='false'
Expand Down
Loading
Loading