From cbb7186fb6e2f6d640f50d6f38cf9fe20e0454dd Mon Sep 17 00:00:00 2001 From: wenshao Date: Sat, 15 Aug 2026 21:20:21 +0000 Subject: [PATCH] fix(autofix): prefer explicit measured= over created_at fallback in the per-run growth collapse --- .github/workflows/qwen-autofix.yml | 11 ++- scripts/tests/qwen-autofix-workflow.test.js | 87 +++++++++++++++++++-- 2 files changed, 89 insertions(+), 9 deletions(-) diff --git a/.github/workflows/qwen-autofix.yml b/.github/workflows/qwen-autofix.yml index 17a253828c..9c13e2d951 100644 --- a/.github/workflows/qwen-autofix.yml +++ b/.github/workflows/qwen-autofix.yml @@ -4655,7 +4655,12 @@ jobs: # collapses to its LATEST measurement — and that collapse happens # BEFORE the over/window/cutoff filters, or a re-run that came back # under budget would still be represented by its stale over=true - # attempt. Every distinct address run has a fresh run_id. + # attempt. Within the collapse an explicit measured= beats the + # created_at fallback: a re-run attempt that crashed BEFORE prepare + # posts an inert over=false marker with no measured=, whose fallback + # (post-run) timestamp would otherwise outdate and erase the same + # run's real prepare-time measurement. Every distinct address run + # has a fresh run_id. # round=/eval-watermark are NOT a safe identity — a state-triggered # lane (a persistent merge conflict selects the PR every scan with no # new evaluable feedback) freezes both NEWEST and ROUND, so distinct @@ -4682,8 +4687,8 @@ jobs: read -r OVER_ROUNDS_PRIOR PREV_SUM < <(jq -r --arg ab "${AUTOFIX_BOT}" --arg key "${LIVE_REARM_KEY}" --arg cutoff "${GROWTH_NOW_CUTOFF}" --arg curr "${GITHUB_RUN_ID}" ' [ .[] | select((.user.login // "") == $ab) | . as $c | ($c.body // "") | [ scan("") ] | .[] - | {sum: ((.[0] | tonumber) + (.[1] | tonumber)), over: .[2], round: (.[3] | tonumber), run: (.[4] | tonumber), measured: (.[5] // ($c.created_at // "")), win: .[6]} ] - | group_by(.run) | map(max_by(.measured)) + | {sum: ((.[0] | tonumber) + (.[1] | tonumber)), over: .[2], round: (.[3] | tonumber), run: (.[4] | tonumber), measured: (.[5] // ($c.created_at // "")), explicit: (.[5] != null), win: .[6]} ] + | group_by(.run) | map(max_by([.explicit, .measured])) | map(select(.win == $key and .over == "true")) | map(select(.run != ($curr | tonumber))) | map(select($cutoff == "" or (.measured > $cutoff))) diff --git a/scripts/tests/qwen-autofix-workflow.test.js b/scripts/tests/qwen-autofix-workflow.test.js index c26e9115b4..de5bf9f29d 100644 --- a/scripts/tests/qwen-autofix-workflow.test.js +++ b/scripts/tests/qwen-autofix-workflow.test.js @@ -6236,12 +6236,14 @@ exit 1 expect(divBlock).toBeTruthy(); const dir = mkdtempSync(join(tmpdir(), 'autofix-diverge-')); // Markers carry round= (informational) and run= (GITHUB_RUN_ID) — deduped - // and ordered on run=, the per-workflow-run id: a retry re-posts one run's - // marker (same run → collapses) while distinct address runs have distinct, - // increasing run ids. round=/eval-watermark are NOT a safe identity — a - // state-triggered lane freezes both — so two distinct runs can share - // round= yet must still count twice. Each row also carries an author + - // created_at (the read filters to the bot and to post-base-update markers). + // on run= and ordered on measured= (the prepare-time instant; created_at + // fallback for legacy markers), the per-workflow-run id: a retry or a + // failed job's re-run re-posts one run's marker (same run → collapses to + // its latest measurement) while distinct address runs have distinct run + // ids. round=/eval-watermark are NOT a safe identity — a state-triggered + // lane freezes both — so two distinct runs can share round= yet must still + // count twice. Each row also carries an author + created_at (the read + // filters to the bot and to markers measured after the cutoff). const marker = ( src, test, @@ -6503,6 +6505,79 @@ exit 1 ], }), ).toBe('false 0'); + // Mirror of that case for the FAILURE path's inert marker: a re-run + // attempt that crashed BEFORE prepare posts over=false with NO measured= + // (MEASURED_AT empty), so its fallback is the comment's created_at — + // post-run, hence newer than the same run's prepare-time measured= from + // its earlier attempt. The collapse must prefer an explicit measured= + // over that fallback, or the inert marker erases the run's real + // over-budget count (#9192 R3-1). + expect( + diverge({ + src: 999, + test: 999, + history: [ + { + user: { login: 'qwen-code-dev-bot' }, + created_at: '2026-01-01T02:00:00Z', + body: '', + }, + { + user: { login: 'qwen-code-dev-bot' }, + created_at: '2026-01-01T03:00:00Z', + body: '', + }, + ], + }), + ).toBe('false 1'); + // Same defect at the handoff threshold: two real over-budget priors, the + // second erased by the inert marker — without the explicit-measured + // preference the count drops to 1 and the divergence handoff (div=2) is + // suppressed while the diff keeps climbing. + expect( + diverge({ + src: 500, + test: 300, + history: [ + { + user: { login: 'qwen-code-dev-bot' }, + created_at: '2026-01-01T01:30:00Z', + body: '', + }, + { + user: { login: 'qwen-code-dev-bot' }, + created_at: '2026-01-01T02:00:00Z', + body: '', + }, + { + user: { login: 'qwen-code-dev-bot' }, + created_at: '2026-01-01T03:00:00Z', + body: '', + }, + ], + }), + ).toBe('true 2'); + // …and two LEGACY markers for the same run (neither carries measured=) + // still collapse on the created_at fallback: the explicit-measured + // preference must not disturb fallback-vs-fallback ordering. + expect( + diverge({ + src: 999, + test: 999, + history: [ + { + user: { login: 'qwen-code-dev-bot' }, + created_at: '2026-01-01T02:00:00Z', + body: '', + }, + { + user: { login: 'qwen-code-dev-bot' }, + created_at: '2026-01-01T03:00:00Z', + body: '', + }, + ], + }), + ).toBe('false 0'); // Backward compatibility: a marker posted BEFORE measured= existed still // counts, falling back to its comment's created_at — deploying the // measured= switch must not blank the census of an in-flight window.