mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-21 15:36:50 -05:00
[AI] Fix flaky Compare Sizes CI job
The job intermittently failed with "==> (not found) Artifact: build-stats" due to a race condition: action-wait-for-check waited by commit SHA while action-download-artifact searched by PR number, picking a newer still-running workflow run whose artifacts hadn't been uploaded yet. Fixes: - Use commit SHA instead of PR number for PR artifact downloads to match the exact workflow run that was waited on - Add if_no_artifact_found: warn to all download steps (defense in depth) - Upgrade CLI download steps from v11 to v18 to support this parameter - Add timeoutSeconds/intervalSeconds to all wait-for-check steps - Expand failure detection to cover base branch builds and add warning step for incomplete builds - Make bundle-stats-comment.mjs gracefully skip missing stats files instead of throwing - Add if: !cancelled() to generate/post steps so they run after warnings https://claude.ai/code/session_015TDkitgqM2TsgFSCuGaFuF
This commit is contained in:
50
.github/workflows/size-compare.yml
vendored
50
.github/workflows/size-compare.yml
vendored
@@ -50,6 +50,8 @@ jobs:
|
|||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
checkName: web
|
checkName: web
|
||||||
ref: ${{github.base_ref}}
|
ref: ${{github.base_ref}}
|
||||||
|
timeoutSeconds: 1200
|
||||||
|
intervalSeconds: 30
|
||||||
- name: Wait for ${{github.base_ref}} API build to succeed
|
- name: Wait for ${{github.base_ref}} API build to succeed
|
||||||
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
||||||
id: master-api-build
|
id: master-api-build
|
||||||
@@ -57,6 +59,8 @@ jobs:
|
|||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
checkName: api
|
checkName: api
|
||||||
ref: ${{github.base_ref}}
|
ref: ${{github.base_ref}}
|
||||||
|
timeoutSeconds: 1200
|
||||||
|
intervalSeconds: 30
|
||||||
- name: Wait for ${{github.base_ref}} CLI build to succeed
|
- name: Wait for ${{github.base_ref}} CLI build to succeed
|
||||||
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
||||||
id: master-cli-build
|
id: master-cli-build
|
||||||
@@ -64,6 +68,8 @@ jobs:
|
|||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
checkName: cli
|
checkName: cli
|
||||||
ref: ${{github.base_ref}}
|
ref: ${{github.base_ref}}
|
||||||
|
timeoutSeconds: 1200
|
||||||
|
intervalSeconds: 30
|
||||||
|
|
||||||
- name: Wait for PR build to succeed
|
- name: Wait for PR build to succeed
|
||||||
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
||||||
@@ -72,6 +78,8 @@ jobs:
|
|||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
checkName: web
|
checkName: web
|
||||||
ref: ${{github.event.pull_request.head.sha}}
|
ref: ${{github.event.pull_request.head.sha}}
|
||||||
|
timeoutSeconds: 1200
|
||||||
|
intervalSeconds: 30
|
||||||
- name: Wait for API PR build to succeed
|
- name: Wait for API PR build to succeed
|
||||||
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
||||||
id: wait-for-api-build
|
id: wait-for-api-build
|
||||||
@@ -79,6 +87,8 @@ jobs:
|
|||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
checkName: api
|
checkName: api
|
||||||
ref: ${{github.event.pull_request.head.sha}}
|
ref: ${{github.event.pull_request.head.sha}}
|
||||||
|
timeoutSeconds: 1200
|
||||||
|
intervalSeconds: 30
|
||||||
- name: Wait for CLI PR build to succeed
|
- name: Wait for CLI PR build to succeed
|
||||||
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
uses: fountainhead/action-wait-for-check@5a908a24814494009c4bb27c242ea38c93c593be # v1.2.0
|
||||||
id: wait-for-cli-build
|
id: wait-for-cli-build
|
||||||
@@ -86,12 +96,32 @@ jobs:
|
|||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
checkName: cli
|
checkName: cli
|
||||||
ref: ${{github.event.pull_request.head.sha}}
|
ref: ${{github.event.pull_request.head.sha}}
|
||||||
|
timeoutSeconds: 1200
|
||||||
|
intervalSeconds: 30
|
||||||
|
|
||||||
- name: Report build failure
|
- name: Report build failure
|
||||||
if: steps.wait-for-web-build.outputs.conclusion == 'failure' || steps.wait-for-api-build.outputs.conclusion == 'failure' || steps.wait-for-cli-build.outputs.conclusion == 'failure'
|
if: |
|
||||||
|
steps.wait-for-web-build.outputs.conclusion == 'failure' ||
|
||||||
|
steps.wait-for-api-build.outputs.conclusion == 'failure' ||
|
||||||
|
steps.wait-for-cli-build.outputs.conclusion == 'failure' ||
|
||||||
|
steps.master-web-build.outputs.conclusion == 'failure' ||
|
||||||
|
steps.master-api-build.outputs.conclusion == 'failure' ||
|
||||||
|
steps.master-cli-build.outputs.conclusion == 'failure'
|
||||||
run: |
|
run: |
|
||||||
echo "Build failed on PR branch or ${{github.base_ref}}"
|
echo "Build failed on PR branch or ${{github.base_ref}}"
|
||||||
exit 1
|
exit 1
|
||||||
|
- name: Warn on incomplete builds
|
||||||
|
if: |
|
||||||
|
steps.wait-for-web-build.outputs.conclusion != 'success' ||
|
||||||
|
steps.wait-for-api-build.outputs.conclusion != 'success' ||
|
||||||
|
steps.wait-for-cli-build.outputs.conclusion != 'success' ||
|
||||||
|
steps.master-web-build.outputs.conclusion != 'success' ||
|
||||||
|
steps.master-api-build.outputs.conclusion != 'success' ||
|
||||||
|
steps.master-cli-build.outputs.conclusion != 'success'
|
||||||
|
run: |
|
||||||
|
echo "::warning::Some builds did not complete successfully. Bundle stats may be incomplete."
|
||||||
|
echo "Base branch - web: ${{ steps.master-web-build.outputs.conclusion }}, api: ${{ steps.master-api-build.outputs.conclusion }}, cli: ${{ steps.master-cli-build.outputs.conclusion }}"
|
||||||
|
echo "PR - web: ${{ steps.wait-for-web-build.outputs.conclusion }}, api: ${{ steps.wait-for-api-build.outputs.conclusion }}, cli: ${{ steps.wait-for-cli-build.outputs.conclusion }}"
|
||||||
|
|
||||||
- name: Download web build artifact from ${{github.base_ref}}
|
- name: Download web build artifact from ${{github.base_ref}}
|
||||||
uses: dawidd6/action-download-artifact@1f8785ff7a5130826f848e7f72725c85d241860f # v18
|
uses: dawidd6/action-download-artifact@1f8785ff7a5130826f848e7f72725c85d241860f # v18
|
||||||
@@ -102,6 +132,7 @@ jobs:
|
|||||||
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
|
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
|
||||||
name: build-stats
|
name: build-stats
|
||||||
path: base
|
path: base
|
||||||
|
if_no_artifact_found: warn
|
||||||
- name: Download API build artifact from ${{github.base_ref}}
|
- name: Download API build artifact from ${{github.base_ref}}
|
||||||
uses: dawidd6/action-download-artifact@1f8785ff7a5130826f848e7f72725c85d241860f # v18
|
uses: dawidd6/action-download-artifact@1f8785ff7a5130826f848e7f72725c85d241860f # v18
|
||||||
id: pr-api-build
|
id: pr-api-build
|
||||||
@@ -111,41 +142,46 @@ jobs:
|
|||||||
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
|
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
|
||||||
name: api-build-stats
|
name: api-build-stats
|
||||||
path: base
|
path: base
|
||||||
|
if_no_artifact_found: warn
|
||||||
- name: Download build stats from PR
|
- name: Download build stats from PR
|
||||||
uses: dawidd6/action-download-artifact@1f8785ff7a5130826f848e7f72725c85d241860f # v18
|
uses: dawidd6/action-download-artifact@1f8785ff7a5130826f848e7f72725c85d241860f # v18
|
||||||
with:
|
with:
|
||||||
pr: ${{github.event.pull_request.number}}
|
commit: ${{github.event.pull_request.head.sha}}
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
|
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
|
||||||
name: build-stats
|
name: build-stats
|
||||||
path: head
|
path: head
|
||||||
allow_forks: true
|
allow_forks: true
|
||||||
|
if_no_artifact_found: warn
|
||||||
- name: Download API stats from PR
|
- name: Download API stats from PR
|
||||||
uses: dawidd6/action-download-artifact@1f8785ff7a5130826f848e7f72725c85d241860f # v18
|
uses: dawidd6/action-download-artifact@1f8785ff7a5130826f848e7f72725c85d241860f # v18
|
||||||
with:
|
with:
|
||||||
pr: ${{github.event.pull_request.number}}
|
commit: ${{github.event.pull_request.head.sha}}
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
|
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
|
||||||
name: api-build-stats
|
name: api-build-stats
|
||||||
path: head
|
path: head
|
||||||
allow_forks: true
|
allow_forks: true
|
||||||
|
if_no_artifact_found: warn
|
||||||
- name: Download CLI build artifact from ${{github.base_ref}}
|
- name: Download CLI build artifact from ${{github.base_ref}}
|
||||||
uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11
|
uses: dawidd6/action-download-artifact@1f8785ff7a5130826f848e7f72725c85d241860f # v18
|
||||||
with:
|
with:
|
||||||
branch: ${{github.base_ref}}
|
branch: ${{github.base_ref}}
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
|
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
|
||||||
name: cli-build-stats
|
name: cli-build-stats
|
||||||
path: base
|
path: base
|
||||||
|
if_no_artifact_found: warn
|
||||||
- name: Download CLI stats from PR
|
- name: Download CLI stats from PR
|
||||||
uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11
|
uses: dawidd6/action-download-artifact@1f8785ff7a5130826f848e7f72725c85d241860f # v18
|
||||||
with:
|
with:
|
||||||
pr: ${{github.event.pull_request.number}}
|
commit: ${{github.event.pull_request.head.sha}}
|
||||||
workflow: build.yml
|
workflow: build.yml
|
||||||
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
|
workflow_conclusion: '' # ignore the conclusion of the workflow, since we already checked it
|
||||||
name: cli-build-stats
|
name: cli-build-stats
|
||||||
path: head
|
path: head
|
||||||
allow_forks: true
|
allow_forks: true
|
||||||
|
if_no_artifact_found: warn
|
||||||
- name: Strip content hashes from stats files
|
- name: Strip content hashes from stats files
|
||||||
run: |
|
run: |
|
||||||
if [ -f ./head/web-stats.json ]; then
|
if [ -f ./head/web-stats.json ]; then
|
||||||
@@ -162,6 +198,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
- name: Generate combined bundle stats comment
|
- name: Generate combined bundle stats comment
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
run: |
|
run: |
|
||||||
node packages/ci-actions/bin/bundle-stats-comment.mjs \
|
node packages/ci-actions/bin/bundle-stats-comment.mjs \
|
||||||
--base desktop-client=./base/web-stats.json \
|
--base desktop-client=./base/web-stats.json \
|
||||||
@@ -175,6 +212,7 @@ jobs:
|
|||||||
--identifier combined \
|
--identifier combined \
|
||||||
--format pr-body > bundle-stats-comment.md
|
--format pr-body > bundle-stats-comment.md
|
||||||
- name: Post combined bundle stats comment
|
- name: Post combined bundle stats comment
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* Heavily inspired by https://github.com/twk3/rollup-size-compare-action (MIT).
|
* Heavily inspired by https://github.com/twk3/rollup-size-compare-action (MIT).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { readFile } from 'node:fs/promises';
|
import { access, readFile } from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import process from 'node:process';
|
import process from 'node:process';
|
||||||
|
|
||||||
@@ -179,8 +179,19 @@ function parseArgs(argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadStats(filePath) {
|
async function loadStats(filePath) {
|
||||||
|
const absolutePath = path.resolve(process.cwd(), filePath);
|
||||||
|
|
||||||
|
// Check if the file exists before trying to read it
|
||||||
|
try {
|
||||||
|
await access(absolutePath);
|
||||||
|
} catch {
|
||||||
|
console.error(
|
||||||
|
`[bundle-stats] Stats file not found: "${filePath}" — skipping`,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const absolutePath = path.resolve(process.cwd(), filePath);
|
|
||||||
const fileContents = await readFile(absolutePath, 'utf8');
|
const fileContents = await readFile(absolutePath, 'utf8');
|
||||||
const parsed = JSON.parse(fileContents);
|
const parsed = JSON.parse(fileContents);
|
||||||
|
|
||||||
@@ -196,7 +207,7 @@ async function loadStats(filePath) {
|
|||||||
? error.message
|
? error.message
|
||||||
: 'Unknown error while parsing stats file';
|
: 'Unknown error while parsing stats file';
|
||||||
console.error(`[bundle-stats] Failed to parse "${filePath}": ${message}`);
|
console.error(`[bundle-stats] Failed to parse "${filePath}": ${message}`);
|
||||||
throw new Error(`Failed to load stats file "${filePath}": ${message}`);
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -687,6 +698,13 @@ async function main() {
|
|||||||
);
|
);
|
||||||
const headStats = await loadStats(section.headPath);
|
const headStats = await loadStats(section.headPath);
|
||||||
|
|
||||||
|
if (!baseStats || !headStats) {
|
||||||
|
console.error(
|
||||||
|
`[bundle-stats] Skipping section "${section.name}": missing ${!baseStats ? 'base' : 'head'} stats`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const statsDiff = getStatsDiff(baseStats, headStats);
|
const statsDiff = getStatsDiff(baseStats, headStats);
|
||||||
const chunkDiff = getChunkModuleDiff(baseStats, headStats);
|
const chunkDiff = getChunkModuleDiff(baseStats, headStats);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user