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
69 changes: 64 additions & 5 deletions .github/scripts/check-workflow-size.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,42 @@ GROWTH_ALLOWANCE="${WORKFLOW_SIZE_GROWTH_ALLOWANCE:-4096}"
# the slack for the next unreviewed 25 KB.
SLACK_BYTES=20000

# The ratchet compares the worktree against a checked-in baseline, so a
# workflow that grew on main without the same-PR baseline bump leaves every
# OTHER open PR failing a gate on a file it never touched (red-walled the
# queue twice in two weeks: #9747, #9822). When the caller passes the PR's
# base commit in WORKFLOW_SIZE_BASE_SHA, the missing-entry and growth
# branches below hard-fail only if the PR actually changed the file; a
# byte-identical copy means the staleness is main-side drift and earns a
# warning instead. An unresolvable base (local run, fetch failure) falls
# back to the strict failure — the ratchet fails closed, never open. One
# residual window stays by design: if main edits the same workflow again
# after the PR branched, the comparison against the new base sees the PR's
# older copy as different and fails closed until that PR rebases —
# self-healing, and still fail-closed, so it is left alone rather than
# wiring the PR's changed-files list into a gate that today needs no API
# call.
BASE_SHA="${WORKFLOW_SIZE_BASE_SHA:-}"
# Returns 0 when the worktree copy of $1 is byte-identical to the base
# commit, 1 when it differs (or no base was given), and 2 when the base
# cannot be resolved — the callers add a diagnostic on 2, because a
# transient fetch failure and genuine PR growth need opposite remedies.
file_matches_base() {
local file="$1"
[[ -n "${BASE_SHA}" ]] || return 1
if ! git rev-parse --verify --quiet "${BASE_SHA}^{commit}" >/dev/null &&
! git fetch --depth=1 --quiet origin "${BASE_SHA}"; then
return 2
fi
git show "${BASE_SHA}:${file}" 2>/dev/null | cmp -s - "${file}"
}

unresolvable_base_note() {
echo "::warning::base ${BASE_SHA} could not be resolved (git fetch failed?) — failing strict; if this PR did not touch ${1}, re-run the job."
}

status=0
warned_stale=0
declare -A baseline=()
if [[ -r "${BASELINE_FILE}" ]]; then
# The || clause keeps an unterminated final line, which read reports as a
Expand Down Expand Up @@ -72,17 +107,41 @@ for file in .github/workflows/*.yml .github/workflows/*.yaml; do

base="${baseline[${file##*/}]:-}"
if [[ -z "${base}" ]]; then
echo "::error file=${file}::${file} has no entry in ${BASELINE_FILE}. Add '${size} ${file##*/}' so its growth is tracked."
status=1
file_matches_base "${file}"
match=$?
if ((match == 0)); then
echo "::warning file=${file}::${file} has no entry in ${BASELINE_FILE}, but the file is unchanged from this PR's base — add '${size} ${file##*/}' on main so its growth is tracked; unrelated PRs are not blocked."
warned_stale=1
else
echo "::error file=${file}::${file} has no entry in ${BASELINE_FILE}. Add '${size} ${file##*/}' so its growth is tracked."
status=1
if ((match == 2)); then
unresolvable_base_note "${file}"
fi
fi
elif ((size > base + GROWTH_ALLOWANCE)); then
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."
status=1
file_matches_base "${file}"
match=$?
if ((match == 0)); then
echo "::warning file=${file}::${file} is ${size} bytes, $((size - base)) over its recorded ${base}, but the file is unchanged from this PR's base — the baseline went stale on main, not in this PR. Bump ${BASELINE_FILE} on main (a one-line PR saying why); unrelated PRs are not blocked."

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.

[Critical] The claim added here — "unrelated PRs are not blocked" — is false as long as the vitest mirror in scripts/tests/workflow-size.test.js (the '%s is within its baseline allowance' block near line 110) keeps asserting bytes <= recorded + allowance against the checkout's own tree with no base-SHA awareness. The mirror has no skipIf and runs via test:scripts inside npm run test:ci on every full-profile lane, and merge_group events always classify full, so the merge-queue lanes run it too. The next time a baseline goes stale on main — the exact #9904 condition this PR exists to fix — the shell gate will warn, but the mirror still fails the run: the red wall is only relocated from the gate step into test:ci. The drift is not hypothetical: before this PR's bump, origin/main had ci.yml at 73850 bytes against the 69782 entry — 28 bytes inside the allowance.

witness (scratch-tree probe, drift state simulated: baseline 69782, ci.yml 74203, base SHA resolvable, unrelated-PR shape):
BASE-side (this gate):  ::warning ... ci.yml is 74203 bytes, 4421 over its recorded 69782 ... unrelated PRs are not blocked.   gate-exit=0
PR-side (vitest mirror): FAIL scripts/tests/workflow-size.test.js > .github/workflows/ci.yml is within its baseline allowance
                         AssertionError: expected 74203 to be less than or equal to 73878
                         Tests  1 failed | 181 passed;  vitest-exit=1

Make the two enforcement points agree about the drift case: either give the mirror the same base-scoped leniency (wire WORKFLOW_SIZE_BASE_SHA into the test job's env and pass/skip the allowance assertion when the file is byte-identical to the base, reusing file_matches_base semantics), or — if the mirror is deliberately kept strict as the final enforcer — amend this warning text and the qwen-autofix.md paragraph to say full CI still fails until the one-line baseline-bump PR lands on main. (Only github_ci_only/docs-only PRs escape, because they skip vitest.)

中文说明

此处新增的说法——"unrelated PRs are not blocked"(无关 PR 不会被阻塞)——并不成立:只要 scripts/tests/workflow-size.test.js 中的 vitest 镜像(约第 110 行的 '%s is within its baseline allowance' 代码块)仍然在没有任何 base SHA 感知的情况下,对检出树自身断言 bytes <= recorded + allowance。该镜像没有 skipIf,并经由 npm run test:ci 里的 test:scripts 在每个 full 配置车道上运行;而 merge_group 事件总是被分类为 full,因此合并队列车道同样会运行它。下一次 main 上的基线变陈旧时——也就是本 PR 要修复的 #9904 情形本身——shell 门只会告警,但镜像仍会让整个测试运行失败:红墙只是从门步骤搬进了 test:ci。这种漂移并非假设:在本 PR 上调基线之前,origin/mainci.yml 为 73850 字节,而基线条目是 69782——距允许增量仅差 28 字节。

请让两个执行点对漂移情形保持一致:要么给镜像同样的 base 范围宽容(把 WORKFLOW_SIZE_BASE_SHA 接线进 test 任务的环境变量,当文件与 base 逐字节相同时让允许增量断言通过/跳过,复用 file_matches_base 语义);要么——如果刻意让镜像保持严格、作为最终执行者——就修改此处的告警文案与 qwen-autofix.md 的段落,说明完整 CI 仍会失败,直到那一行基线修复 PR 合入 main。(只有 github_ci_only/纯文档 PR 能幸免,因为它们不跑 vitest。)

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

warned_stale=1
else
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."

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] When WORKFLOW_SIZE_BASE_SHA is set but unresolvable — e.g. a transient git fetch --depth=1 failure — the annotation emitted is byte-identical to genuine PR growth (probe-verified: diff of the two ::error outputs is empty), and the two causes have opposite remedies: re-run the job vs. bump the baseline. This diff adds the network round-trip to a gate that previously had none, on a lane whose adjacent checkout-step comment documents a flaky egress proxy. The fail-closed behavior is right and stays; only the diagnosis needs to improve, otherwise an unrelated PR hit by an infra blip gets told the growth is theirs — re-creating the red wall this PR removes, with a misleading remedy.

witness (probe, size held at 5000):
fixture A (genuine growth) vs fixture B (file committed at 5000, never touched,
WORKFLOW_SIZE_BASE_SHA=0x40 unresolvable)
diff <(grep '::error' A/out.txt) <(grep '::error' B/out.txt) -> no difference (BYTE-IDENTICAL)

Distinguish "base unresolvable" from "content differs" — e.g. return a distinct status for the resolution-failure path — and emit one extra line in that case: echo "::warning::base ${BASE_SHA} could not be resolved (git fetch failed?) — failing strict; if this PR did not touch ${file}, re-run the job."

中文说明

WORKFLOW_SIZE_BASE_SHA 已设置但无法解析时——例如 git fetch --depth=1 瞬时失败——发出的标注与 PR 真实增长逐字节相同(探针验证:两个 ::error 输出的 diff 为空),而两种原因的补救方式恰恰相反:重跑任务 vs 上调基线。本 diff 给一个此前没有任何网络调用的门新增了一次网络往返,且所在车道的相邻 checkout 步骤注释明确记录了出口代理不稳定。失败关闭的行为是正确的、应保留;需要改进的只是诊断——否则一个被基础设施抖动击中的无关 PR 会被告知"增长是你造成的",以误导性的补救方式重新筑起本 PR 要拆除的红墙。

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

status=1
if ((match == 2)); then
unresolvable_base_note "${file}"
fi
fi
elif ((size + SLACK_BYTES < base)); then
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."
fi
done

if ((status == 0)); then
echo "✅ every workflow file is under the ${GATE_BYTES}-byte gate and within ${GROWTH_ALLOWANCE} bytes of its recorded baseline"
if ((warned_stale)); then
echo "✅ every workflow file is under the ${GATE_BYTES}-byte gate (stale-baseline warnings above — update ${BASELINE_FILE} on main)"
else
echo "✅ every workflow file is under the ${GATE_BYTES}-byte gate and within ${GROWTH_ALLOWANCE} bytes of its recorded baseline"
fi
fi
exit "${status}"
2 changes: 1 addition & 1 deletion .github/workflows/.size-baseline
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
4638 build-and-publish-image.yml
49610 cd-cua-driver.yml
2076 cd-mobile-mcp.yml
69782 ci.yml
74315 ci.yml
1482 codeql.yml
9389 comment-attachment-guard.yml
31677 desktop-release.yml
Expand Down
17 changes: 13 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ env:
# new helper test can't be added to one path and silently dropped from the
# other.
HELPER_TESTS: '.github/scripts/pr-safety-precheck.test.mjs .github/scripts/cap-release-notes.test.mjs .github/scripts/ci/classify-profile.test.mjs .github/scripts/ci/classify-pr-profile.test.mjs .github/scripts/upsert-bot-comment.test.mjs .github/scripts/ci/main-failure-signature.test.mjs .github/scripts/classify-release-notes.test.mjs .github/scripts/dsw-swe-verified/make-manifest.test.mjs .github/scripts/dsw-swe-verified/make-terminal-bench-manifest.test.mjs .github/scripts/resolve-sandbox-image.test.mjs .github/scripts/web-shell-visuals-publish.test.mjs .github/scripts/web-shell-visuals-compose.test.mjs .github/scripts/serve-ab-diff.test.mjs .github/scripts/serve-ab-drive.test.mjs .github/scripts/qwen-triage-workflow.test.mjs .github/scripts/assign-issue-owner.test.mjs .github/scripts/auto-minimize-spam.test.mjs .github/scripts/ci-runner-routing.test.mjs'
# The growth ratchet and its vitest mirror compare each workflow against
# the PR's base commit to tell "this PR grew the file" apart from "the
# baseline went stale on main" (#9904). Wired once here so every lane
# inherits it — the gate step and every `npm run test:ci` step, whatever
# it is named — instead of each step hand-wiring a copy.
WORKFLOW_SIZE_BASE_SHA: '${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }}'

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 env wiring added here makes the test job's checkout comment (lines ~270–273: "Shallow: nothing here walks git history (the verify guard below checks head.sha == HEAD, schema/tests touch only the working tree)") false: the size-gate step in this same job now runs git rev-parse / git fetch --depth=1 origin <sha> / git show against this checkout once WORKFLOW_SIZE_BASE_SHA is set, and the vitest mirror in npm run test:ci does the same. The inventory matters because it justifies the checkout tuning: a maintainer hardening this checkout — e.g. setting persist-credentials: false — would make both fetches fail, the ratchet would fail closed, and the stale-baseline red wall this PR removes would return on every stale-baseline PR with nothing in the checkout visibly consuming git to point at the cause. The "depth 1 is enough" conclusion itself still holds; only the stated rationale is stale. Suggested rewrite of the parenthetical:

# Shallow: nothing here walks git history except on demand (the verify guard
# below checks head.sha == HEAD; the size gate and its vitest mirror fetch
# the PR's base commit at depth 1 when the baseline went stale; everything
# else touches only the working tree).
中文说明

此处新增的环境变量接线使 test 任务的检出注释(约第 270–273 行:"Shallow: nothing here walks git history(下方的验证守卫检查 head.sha == HEAD,schema/测试只碰工作树)")不再成立:设置 WORKFLOW_SIZE_BASE_SHA 后,同一任务中的体积门步骤会对这个检出运行 git rev-parse / git fetch --depth=1 origin <sha> / git shownpm run test:ci 中的 vitest 镜像也会做同样的事。这份"清单"之所以重要,是因为它为检出调优提供了理由:一位维护者若据此加固检出——例如设置 persist-credentials: false——会让两个 fetch 都失败、棘轮失败关闭,本 PR 要消除的陈旧基线红墙会在每个基线陈旧的 PR 上重现,而检出中却看不到任何明显消费 git 的地方。"depth 1 足够"的结论本身仍然成立,只是写出的理由已经过时。建议把括号内的说明改写为:

# Shallow: nothing here walks git history except on demand (the verify guard
# below checks head.sha == HEAD; the size gate and its vitest mirror fetch
# the PR's base commit at depth 1 when the baseline went stale; everything
# else touches only the working tree).

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


jobs:
classify_pr:
Expand Down Expand Up @@ -261,10 +267,13 @@ jobs:
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
ref: "${{ github.event.inputs.branch_ref || (github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number)) || (github.event_name == 'merge_group' && github.event.merge_group.head_sha) || github.ref }}"
# Shallow: nothing here walks git history (the verify guard below checks
# head.sha == HEAD, schema/tests touch only the working tree). On the
# in-repo ECS runner a full-history clone is the heaviest transfer and
# chokes the squid egress proxy, flaking checkout. depth 1 is enough.
# Shallow: nothing here walks git history except on demand (the
# verify guard below checks head.sha == HEAD; the size gate and its
# vitest mirror fetch the PR's base commit at depth 1 when the
# baseline went stale; everything else touches only the working
# tree). On the in-repo ECS runner a full-history clone is the
# heaviest transfer and chokes the squid egress proxy, flaking
# checkout. depth 1 is enough.
fetch-depth: 1

# Guard against a stale checkout (e.g. a caching egress proxy serving an old
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/qwen-autofix.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ fails until the number is updated in the same PR. Growing a file is still
allowed — the ratchet only insists the growth be visible in review rather than
discovered at the wall.

The ratchet's blast radius is scoped to the PR that earned it (#9904). The
comparison above is worktree-vs-checked-in-baseline, so a file that grew on
main without the same-PR bump would otherwise fail every unrelated open PR on
a file it never touched — that red-walled the queue twice in two weeks
(#9747, #9822). Given the PR's base commit (`WORKFLOW_SIZE_BASE_SHA`, wired
in `ci.yml`), the gate hard-fails only when the PR's copy of the file differs
from the base; a byte-identical copy means the staleness is main-side drift
and earns a warning pointing at the one-line baseline-bump PR instead. An
unresolvable base keeps the strict failure — the gate fails closed.

### Steps that moved out, not just their prose

`review-address` · `Push and report` was 626 lines of inline shell — ~41 KB,
Expand Down
Loading
Loading