Commit graph

3 commits

Author SHA1 Message Date
Shaojin Wen
a5d77eb817
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>
2026-08-22 16:31:56 +00:00
Shaojin Wen
cef54b8340
refactor(autofix): move the push-and-report body out of the workflow file (#9653)
* refactor(autofix): move the push-and-report body out of the workflow file

qwen-autofix.yml was 462,656 bytes — 90% of GitHub's 500 KB start-runs
limit and inside the 470,000-byte gate's warning band. Past that limit
GitHub stops starting runs and says nothing: schedule ticks vanish,
dispatches sit queued with zero jobs, and only PR-event runs keep working
because they resolve an older copy from the PR's branch. The file crossed
it once already, on 2026-08-19, and the loop went dark for a day.

'Push and report' was the largest single block in the file at 626 lines of
inline shell. Its body moves to .github/scripts/autofix-push-and-report.sh
byte-identically — the YAML keeps the step's if: and env:, which is where
the questions of when it runs and what reaches it belong. The file drops
to 422,447 bytes.

Extraction moves the trust problem with it. By the time this step runs the
agent and the verification gate have executed branch code on this host, so
the workspace copy is branch-controlled. The step runs the trusted-base
copy staged before any of that, after proving its digest — recorded in
GITHUB_OUTPUT, which a disk write cannot reach — still matches, that the
staged path is a regular file, and with both reads bounded so a planted
FIFO is a refusal rather than a hang until timeout-minutes. A new contract
test pins all of that, including that the workspace copy is never what
runs; each of its assertions was checked against a mutant of the line it
guards.

The census assertions that counted sites across the workflow now count the
script too, so an extraction cannot silently drop a site from a count that
exists to pin it. The two emit_growth_audit_marker copies are compared
dedented, since one is now at column 0 and its twin is still at ten.

This is also the step docs/design/autofix-gate-runner-isolation.md moves
into its own publish job. Carrying it as a file makes that a small diff,
and deletes the staging scaffolding added here: a job that checks out the
trusted base and never executes branch code can run the script where it
lies.

Refs #9089.

* fix(autofix): close the push-and-report gate's env-import and TOCTOU holes

The wrapper verified the staged script in the step's inherited shell:
a $GITHUB_ENV-planted PATH or BASH_FUNC_* import could swap the gate's
own words, and the staged path was opened three times, so a watcher
could swap the file between the digest check and the execution read.
Run the whole verify-and-run sequence in one env -i clean child that
opens the staged path once, verifies the digest of the captured bytes,
and executes those same bytes. Also fail staging closed on any cp
failure, fix the script's env contract, and pin the new guards.

* fix(autofix): close the push-and-report gate's silent no-op hole

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(autofix): pin gate pass-through, refusal order, and continuation bans (#9653)

Address review suggestions on the push-and-report extraction:

- R2-3: pin the gate child's env -i pass-through list as a sorted
  allow-list equality check (R9-10 precedent), so a dropped variable
  fails loudly instead of expanding empty.
- R4-4: pin the sentinel refusal BEFORE the round_reported write —
  shape-only pins let the ordering rot (probe-verified mutant).
- R4-6: make the execution bans continuation-tolerant; bash joins
  `\<newline>` before tokenizing, so line-continued spellings evaded
  the `[^\n|]*` middle (probe: old regex misses, new catches).
- R1-6: re-scope the paired R10-8 upsert negatives to both halves,
  matching the positive censuses.
- R3-2: correct the stale workflow-size figure (462,656 -> 463,004
  bytes at the post-merge base; 90%/98% still hold).
- R1-5/R1-15/R1-11: correct three disproved claims in the trust-model
  doc (block rank, GITHUB_OUTPUT qualifier, which call sites hold the
  single-open shape).

* refactor(autofix): deliver the push-and-report body as content, not a staged file

The extraction put the body in a file and then had to defend that file: a
staged copy under RUNNER_TEMP, a digest recorded in step output, a type
check, bounded reads, and — after three rounds of findings against those —
an env -i clean child with a liveness sentinel and a 38-entry pass-through
list wrapping the whole verify-and-run sequence. Four review rounds have
now been spent on that machinery, and the open findings against it are
about the machinery, not about the move: output buffering that costs the
live log the inline body had, an env allow-list nothing pins, and pin gaps
in the tests that exist only to hold the gate together.

Delete the object being guarded instead. The stage step already reads the
trusted base before any branch code runs, so it captures the body as text
into step output and the step runs those bytes — the delivery the inline
block had, and the one upsert-deferred-issue.sh already uses. With no
agent-writable copy on disk there is nothing to stage, digest, type-check
or re-open, and no check→use window between the steps: R1-2's TOCTOU,
R1-13's FIFO double-open, R2-2/R2-5/R2-6/R2-7's single-open pins and
R4-1's buffering all describe a mechanism that no longer exists.

This is not a security regression. What it drops is protection the inline
block never had; what it keeps is the one property that matters — the
bytes come from the trusted-base checkout through expression context, not
from a path the branch can write. The step shell's inherited environment
(R1-1, R1-3) is the status quo this PR preserves rather than the thing an
extraction should be fixing: #9525's publish job takes the PAT out of that
shell entirely, which is the real close.

Kept from the gate work: the round_reported output. A loader plant that
kills this shell at execve exits 0 having written nothing, so 'Finalize
autofix status comment' can still tell a published round from a no-op —
now without a sentinel, because the write is simply last.

Also corrected, from the same review: the header no longer claims the
block was the largest in the file (it is third, after 'Scan for PRs with
new feedback' and 'Prepare branch and feedback'), the size is measured at
this branch's merge-base (462,720), the env contract is stated as a rule
rather than a list that went stale within one round, and the shellcheck
note says which lane and which severity it is clean under. The R10-8
execution-ban negatives now cover the scripts as well as the workflow, and
the script's own flag line is pinned as a whole rather than by spelling.

Refs #9089, #9525.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-22 10:23:46 +00:00
Shaojin Wen
3b3818db87
fix(ci): keep qwen-autofix.yml under GitHub's 500 KB start-runs limit (#9517)
GitHub does not start runs for a workflow file larger than 500 KB (512,000
bytes) and reports nothing when it stops. qwen-autofix.yml crossed that line
on 2026-08-19 at 512,782 bytes: schedule ticks stopped firing, every
workflow_dispatch sat "queued" forever with zero jobs and could not be
cancelled, and issues/issue_comment went quiet — while pull_request_review
runs kept succeeding, because a PR event resolves the workflow from the PR's
own branch and those carry older, smaller copies of this file. The loop
therefore looked half-alive and stayed dark for a day.

Move 75 long comment blocks (1,326 lines) verbatim into a sibling design
record, .github/workflows/qwen-autofix.md, leaving each block's opening lines
plus a `qwen-autofix.md#af-NNN` pointer where it sat: 518,055 -> 426,437
bytes. No executable line changes — the YAML parses to an identical document
outside `run:`, every `run:` script still passes `bash -n`, and the only lines
removed anywhere are comments. Steps that are duplicated verbatim across jobs
share one pointer so they stay byte-identical.

Add .github/scripts/check-workflow-size.sh (gate at 470,000 bytes), wired into
CI on every profile: a .github-only PR classifies as `github_ci_only` and
skips the `full`-only checks, which is exactly the PR that can trip this.
Tests pin the gate, every workflow's size, and pointer/section symmetry.

Delete qwen-autofix-recovery.yml. It was cloned during the incident on the
theory that the workflow ENTITY was wedged, but it carried the same oversized
file, so its dispatches queued identically and its schedule never fired.
2026-08-20 01:56:45 +00:00