Skip to content

Commit a5d77eb

Browse files
wenshaoqwen-code-dev-botqwencoder
authored
chore(ci): migrate autofix prose to the design record and ratchet growth (#9677)
* chore(ci): migrate autofix prose to the design record and ratchet growth `qwen-autofix.yml` was at 462,720 bytes — 90% of GitHub's 500 KB start-runs limit and 7,280 under this repo's gate. The prose migration in #9517 had regained 78 KB; a single feature commit gave 25 KB of it back two days later, 53% of it comment lines that belonged in the design record. Nothing objected, because the gate is a ceiling and only speaks when a file is already at the wall. Two changes, addressing the level and the slope. Level: 76 comment blocks of 8+ lines move into qwen-autofix.md as af-073..af-148, each leaving its opening two lines plus the usual pointer. The file drops to 419,995 — 50 KB under the gate. Slope: every workflow's recorded size now lives in .github/workflows/.size-baseline, and check-workflow-size.sh fails a file that exceeds its entry by more than 4 KB. Growing a file is still allowed; the ratchet only insists the growth be visible in review instead of discovered at the wall. A file well under its baseline warns so the slack is reclaimed rather than banked. Two things the migration had to learn, both caught by contract tests rather than by inspection: Identical prose shares one af id. A step inlined into several jobs must stay byte-identical across copies, and minting a separate id per copy broke that for the git-config sanitize step — same length, different pointer digits. A cross-file editing contract is not prose. "This copy and the one in qwen-triage must be edited together" only does its job where the editor will see it; moving it to the design record is how a lockstep silently breaks. Blocks naming a sibling workflow or script, or spelling the contract out, stay put. Behaviour is unchanged and checked, not assumed: both YAML documents were parsed and compared with comment lines dropped from every string, and they are equal. * fix(ci): harden the workflow-size ratchet per review - key the vitest baseline lookups by separator-agnostic file name so the merge-queue Windows lane resolves entries (split('/') missed win32 joins) - fail closed on malformed .size-baseline lines: non-numeric values, leading zeros (bash octal), and extra fields no longer disable or mis-key the ratchet; keep an unterminated final line - execute the gate script end-to-end in tests (growth, missing entry, missing baseline, slack warning, malformed line) so its decision branches are witnessed - correct af-079/af-123/af-084 attributions in the design record * fix(ci): pin ratchet boundaries and skip pre-bash-4 test hosts * fix(ci): end migrated autofix teasers at sentence boundaries per review --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
1 parent 6c742ec commit a5d77eb

6 files changed

Lines changed: 2064 additions & 611 deletions

File tree

.github/scripts/autofix-push-and-report.sh

Lines changed: 17 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
# Push the round's commit to the PR head and post the round report.
33
#
44
# The body below is the 'Push and report' step of review-address in
5-
# .github/workflows/qwen-autofix.yml, byte-identical to the inline block it
6-
# came from: 626 lines, ~41 KB. The file it left is within a few KB of the
7-
# repo's 470,000-byte gate, and GitHub stops starting runs past 512,000 without
8-
# saying so (.github/scripts/check-workflow-size.sh). No absolute size is
5+
# .github/workflows/qwen-autofix.yml — the inline block it came from (626
6+
# lines, ~41 KB at the move), its long comments since migrated to
7+
# qwen-autofix.md pointers like the rest of the workflow. The file it left
8+
# is within a few KB of the repo's 470,000-byte gate, and GitHub stops starting
9+
# runs past 512,000 without saying so (.github/scripts/check-workflow-size.sh).
10+
# No absolute size is
911
# quoted here on purpose — main moves it every day, and a number that decays is
1012
# how this comment earned three review rounds. It is also
1113
# the step docs/design/autofix-gate-runner-isolation.md moves into its own
@@ -78,15 +80,8 @@ MODEL_DISPLAY="${MODEL:-default}"
7880
# Growth-audit trail (+ re-arm on sound): audit rounds record the
7981
# verdict under the key the baseline was READ under — same rule as
8082
# the growth markers, same dead-key hazard (a supersede-exempt
81-
# round can report under a stale WINDOW after a re-arm). The
82-
# verdict comes from AUDIT_VERDICT — the verdict the verification
83-
# GATE validated and surfaced as a step output — NOT a re-read of
84-
# growth-audit.json: the branch's own build/tests run as the runner
85-
# user and WORKDIR is a predictable path they can write, so the
86-
# file could change after the gate looked. Re-arming is allowed
87-
# for completed rounds only ($1 = allow): a sound verdict whose
88-
# round then FAILED must not re-anchor the window — the failure
89-
# path re-measures under the same window instead.
83+
# round can report under a stale WINDOW after a re-arm).
84+
# Full rationale → qwen-autofix.md#af-131
9085
emit_growth_audit_marker() {
9186
local allow_rearm="${1:-false}"
9287
[[ "${KISS_AUDIT}" == 'true' ]] || return 0
@@ -233,12 +228,7 @@ resolve_and_reply_threads() {
233228
fi
234229
# The mirror of the resolve above: a finding the agent did NOT
235230
# resolve keeps its thread open, and this answers it IN that thread.
236-
# Without it the reason sits only in the round summary, so the
237-
# reviewer who opens the still-open thread sees silence and cannot
238-
# tell their finding was read. Same neutralisation as the summary
239-
# body — a reply is model output posted verbatim under the bot
240-
# identity, so it could otherwise smuggle a forged control marker.
241-
# Best-effort: a reply failure must never fail a good push.
231+
# Full rationale → qwen-autofix.md#af-132
242232
if [[ -s "${WORKDIR}/comment-replies.json" ]] &&
243233
jq -e 'type == "array"' "${WORKDIR}/comment-replies.json" > /dev/null 2>&1; then
244234
REPLIED_N=0
@@ -271,12 +261,8 @@ resolve_and_reply_threads() {
271261
# later round whose agent rewrites an unchanged declination
272262
# must not post the same bot reply twice on one thread
273263
# (observed 2026-08-16: an identical reply posted three
274-
# times, #9296). Skip when the thread already carries a
275-
# comment by the bot whose body EQUALS the neutralised body
276-
# about to be posted; a changed body — a new reason in a
277-
# later round — still posts. Best-effort like the rest: with
278-
# a stale or empty threads view this degrades to the old
279-
# post-always behavior.
264+
# times, #9296).
265+
# Full rationale → qwen-autofix.md#af-133
280266
if jq -e --argjson id "${root_id}" --arg bot "${AUTOFIX_BOT}" \
281267
--arg body "${REPLY_BODY}" '
282268
map(select(any(.comments.nodes[]; .databaseId == $id)))
@@ -382,13 +368,8 @@ bash "${RUNNER_TEMP}/resanitize-git-config.sh"
382368
if [[ "${OUTCOME}" == "fixed" ]]; then
383369
NEXT_ROUND="$(( ROUND + 1 ))"
384370
# The tree the gate verified is what gets pushed: assert HEAD is
385-
# the gate's verified_head before touching credentials. A repo
386-
# redirect (a planted .git/commondir/GIT_DIR — the first defused
387-
# by resanitize, the second by the env strip) would otherwise let
388-
# `git rev-parse HEAD` and the push read an attacker repo whose
389-
# HEAD differs; this compares against the value the gate recorded
390-
# in GITHUB_OUTPUT (unreachable from a disk write). Empty
391-
# verified_head only on a noop, which does not reach this push.
371+
# the gate's verified_head before touching credentials.
372+
# Full rationale → qwen-autofix.md#af-134
392373
HEAD_NOW="$(git rev-parse HEAD)"
393374
if [[ -z "${VERIFIED_HEAD}" || "${HEAD_NOW}" != "${VERIFIED_HEAD}" ]]; then
394375
echo "::error::HEAD ${HEAD_NOW} is not the gate's verified head ${VERIFIED_HEAD:-<empty>} — refusing to push"
@@ -560,13 +541,8 @@ fi
560541

561542
# Bounded retry on the report post: this one comment carries the
562543
# round's ENTIRE persisted state (autofix-eval watermark/round,
563-
# redcheck head, growth baseline). The push has already landed, so
564-
# a transient API failure here loses the marker while keeping the
565-
# growth — the retry scan would re-anchor the baseline at the
566-
# post-push size and re-evaluate feedback it already addressed.
567-
# Three attempts bound that to genuine outages; the final failure
568-
# keeps today's semantics (step fails, no marker, next scan
569-
# retries the round).
544+
# redcheck head, growth baseline).
545+
# Full rationale → qwen-autofix.md#af-135
570546
REPORT_POSTED='false'
571547
for attempt in 1 2 3; do
572548
if gh pr comment "${PR}" --repo "${REPO}" --body-file "${WORKDIR}/report.md"; then
@@ -590,13 +566,8 @@ if [[ "${OUTCOME}" == "fixed" && "${MAX_ROUNDS}" == "${TAKEOVER_MAX_ROUNDS}" ]]
590566
# Crossing trigger, not an equality test: failure rounds also
591567
# advance the round counter, so `push@9, crash@10, push@11`
592568
# would skip an exact %10 check forever — and a failure-heavy
593-
# PR is the very PR the digest exists for. Post on the first
594-
# PUSHED round once 10+ rounds have accumulated since the last
595-
# digest in THIS window (or since the window opened). The
596-
# window opens at the round SEED, not at zero: a '/takeover
597-
# from 60' counter starts at 60, so the no-digest-yet baseline
598-
# is the seed — otherwise the seed-inflated counter digests on
599-
# the window's first push with a 1-2 round census.
569+
# PR is the very PR the digest exists for.
570+
# Full rationale → qwen-autofix.md#af-136
600571
MS_LAST="$(jq -r --arg ab "${AUTOFIX_BOT}" --arg win "${WINDOW:-none}" --argjson start "${ROUND_START:-0}" '
601572
[ .[] | select((.user.login // "") == $ab) | (.body // "")
602573
| [ scan("<!-- autofix-milestone round=([0-9]+) win=([^ ]+) -->") ] | .[]

.github/scripts/check-workflow-size.sh

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,73 @@ GITHUB_LIMIT_BYTES=512000
1616
GATE_BYTES="${WORKFLOW_SIZE_GATE_BYTES:-470000}"
1717
WARN_BYTES=$((GATE_BYTES - 25000))
1818

19+
# The gate above is the ceiling; the baseline below is the RATCHET. The gate
20+
# alone only objects once a file is nearly at the wall, so growth accumulates
21+
# invisibly until one unlucky PR has to pay for everyone: qwen-autofix.yml
22+
# regained 78 KB when its prose moved out (#9517) and gave 25 KB of it back in
23+
# a single feature commit two days later, unremarked. Each file's recorded size
24+
# lives in .size-baseline; exceeding it by more than the allowance fails until
25+
# the number is updated in the same PR, which turns the drift into one line a
26+
# reviewer sees.
27+
BASELINE_FILE='.github/workflows/.size-baseline'
28+
GROWTH_ALLOWANCE="${WORKFLOW_SIZE_GROWTH_ALLOWANCE:-4096}"
29+
# Loose enough that ordinary edits do not churn the manifest, tight enough that
30+
# a file which shed real weight gets its baseline reclaimed rather than banking
31+
# the slack for the next unreviewed 25 KB.
32+
SLACK_BYTES=20000
33+
1934
status=0
35+
declare -A baseline=()
36+
if [[ -r "${BASELINE_FILE}" ]]; then
37+
# The || clause keeps an unterminated final line, which read reports as a
38+
# failure and the loop would otherwise silently drop.
39+
while read -r recorded name extra || [[ -n "${recorded}" ]]; do
40+
[[ -z "${recorded}" || "${recorded}" == \#* ]] && continue
41+
# Fail closed on malformed lines: bash evaluates a leading-zero value as
42+
# OCTAL at the arithmetic sites below, a non-numeric one errors both
43+
# comparisons to false (the ratchet would fail OPEN), and extra fields
44+
# key differently in the vitest mirror.
45+
if [[ -z "${name}" || -n "${extra}" || ! "${recorded}" =~ ^(0|[1-9][0-9]*)$ ]]; then
46+
echo "::error file=${BASELINE_FILE}::${BASELINE_FILE} entry '${recorded}${name:+ ${name}}${extra:+ ${extra}}' is malformed — expected exactly '<bytes> <file>' with a decimal byte count (no leading zeros)"
47+
status=1
48+
continue
49+
fi
50+
baseline["${name}"]="${recorded}"
51+
done <"${BASELINE_FILE}"
52+
else
53+
echo "::error::${BASELINE_FILE} is missing or unreadable — the growth ratchet cannot run"
54+
exit 1
55+
fi
56+
2057
shopt -s nullglob
2158
for file in .github/workflows/*.yml .github/workflows/*.yaml; do
2259
if ! size="$(wc -c <"${file}")"; then
2360
echo "::error file=${file}::unable to read ${file}"
2461
status=1
2562
continue
2663
fi
64+
size="${size// /}"
2765
pct=$((size * 100 / GITHUB_LIMIT_BYTES))
2866
if ((size > GATE_BYTES)); then
2967
echo "::error file=${file}::${file} is ${size} bytes — ${pct}% of GitHub's ${GITHUB_LIMIT_BYTES}-byte start-runs limit, past this repo's ${GATE_BYTES}-byte gate. Move prose into a sibling .md and long steps into .github/scripts/; do not raise the gate."
3068
status=1
3169
elif ((size > WARN_BYTES)); then
3270
echo "::warning file=${file}::${file} is ${size} bytes (${pct}% of GitHub's limit) — approaching the ${GATE_BYTES}-byte gate."
3371
fi
72+
73+
base="${baseline[${file##*/}]:-}"
74+
if [[ -z "${base}" ]]; then
75+
echo "::error file=${file}::${file} has no entry in ${BASELINE_FILE}. Add '${size} ${file##*/}' so its growth is tracked."
76+
status=1
77+
elif ((size > base + GROWTH_ALLOWANCE)); then
78+
echo "::error file=${file}::${file} grew to ${size} bytes, $((size - base)) over its recorded ${base} (allowance ${GROWTH_ALLOWANCE}). Move prose into a sibling .md and long steps into .github/scripts/ — or, if the growth is real, update ${BASELINE_FILE} in this PR and say why."
79+
status=1
80+
elif ((size + SLACK_BYTES < base)); then
81+
echo "::warning file=${file}::${file} is ${size} bytes, $((base - size)) under its recorded ${base} — lower the entry in ${BASELINE_FILE} so the slack is not banked."
82+
fi
3483
done
3584

3685
if ((status == 0)); then
37-
echo "✅ every workflow file is under the ${GATE_BYTES}-byte gate"
86+
echo "✅ every workflow file is under the ${GATE_BYTES}-byte gate and within ${GROWTH_ALLOWANCE} bytes of its recorded baseline"
3887
fi
3988
exit "${status}"

.github/workflows/.size-baseline

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Recorded byte size of each workflow file, enforced by
2+
# .github/scripts/check-workflow-size.sh.
3+
#
4+
# The absolute gate in that script is the ceiling; this file is the ratchet.
5+
# A workflow may not grow past its recorded size by more than the allowance
6+
# without updating the number here IN THE SAME PR — which is the point: it
7+
# turns invisible drift into one reviewed line. qwen-autofix.yml regained
8+
# 78 KB when its prose moved to qwen-autofix.md (#9517), then gave 25 KB of
9+
# it back in a single feature commit two days later, with nothing objecting
10+
# until the next PR ran out of ceiling.
11+
#
12+
# Growing a file is allowed — deliberately. Prefer moving prose into the
13+
# sibling .md and long steps into .github/scripts/ first; if the growth is
14+
# real, bump the number and say why in the PR.
15+
2226 assign-issue-owner.yml
16+
3480 audio-capture-prebuilds.yml
17+
9023 auto-minimize-spam.yml
18+
4638 build-and-publish-image.yml
19+
29715 cd-cua-driver.yml
20+
2076 cd-mobile-mcp.yml
21+
69782 ci.yml
22+
1482 codeql.yml
23+
9389 comment-attachment-guard.yml
24+
31677 desktop-release.yml
25+
2038 docs-page-action.yml
26+
10005 dsw-swe-verified-release.yml
27+
12340 e2e.yml
28+
11394 finalize-release.yml
29+
15871 live-host-release.yml
30+
6384 live-host.yml
31+
7642 main-ci-failure-issue.yml
32+
1686 npm-cache.yml
33+
7299 pr-force-push-reminder.yml
34+
6495 pr-self-report-label.yml
35+
9646 qwen-autofix-fork-bridge.yml
36+
5942 qwen-autofix-fork-signal.yml
37+
392111 qwen-autofix.yml
38+
7061 qwen-ci-flaky-rerun.yml
39+
151937 qwen-code-pr-review.yml
40+
79041 qwen-fleet-shepherd.yml
41+
20525 qwen-issue-followup-bot.yml
42+
5760 qwen-pr-safety-precheck.yml
43+
27648 qwen-triage-finalize.yml
44+
344548 qwen-triage.yml
45+
9657 release-sdk-java.yml
46+
22037 release-sdk-python.yml
47+
19094 release-sdk.yml
48+
14546 release-vscode-companion.yml
49+
39291 release.yml
50+
43717 repo-hygiene.yml
51+
1079 scorecard-monthly.yml
52+
10691 sdk-java.yml
53+
3886 sdk-python.yml
54+
3197 security-checks.yml
55+
6777 serve-ab-publish.yml
56+
17013 serve-ab.yml
57+
2641 stale.yml
58+
8653 sync-cua-driver-to-oss.yml
59+
10920 sync-desktop-to-oss.yml
60+
10018 sync-live-host-to-oss.yml
61+
10138 sync-release-to-oss.yml
62+
3303 update-ecs-runner-qwen.yml
63+
2307 web-shell-visuals-cleanup.yml
64+
15380 web-shell-visuals-publish.yml
65+
16384 web-shell-visuals.yml
66+
4712 windows-runner-smoke.yml

0 commit comments

Comments
 (0)