From d6f1d815980c96757e150ee351ef1fbcaf253463 Mon Sep 17 00:00:00 2001 From: Ytallo Layon Date: Fri, 24 Jul 2026 17:36:52 -0300 Subject: [PATCH 1/2] ci: surface the largest cache entries in the budget report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repository sits at 10.42 GiB against a 10 GiB hard budget, so the `Report cache budget` job fails and GitHub evicts by last-access. The report gave a total but no way to see what was consuming the space, which made the eviction look arbitrary. List the 15 largest entries with their key and ref. On current data that immediately shows 4.01 GiB — 38% of the budget — is Windows release-build caches from `_rust-binary.yml`, written on tag pushes and read only by the next release of the same worker, while the pinned-engine cache that every PR run depends on gets evicted and costs 308s to rebuild each time. Whether to keep those is a judgement call about release build times, so this only makes the trade-off visible. The `ref` column is what tells a reader whether an entry is shared (refs/heads/main) or scoped to one PR. Row capping happens inside the awk pass: piping through `head` would close sort's stdout early and SIGPIPE it under `pipefail`. --- .github/workflows/cache-warm.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/cache-warm.yml b/.github/workflows/cache-warm.yml index 9b7384731..e783a732a 100644 --- a/.github/workflows/cache-warm.yml +++ b/.github/workflows/cache-warm.yml @@ -218,6 +218,27 @@ jobs: echo "- Target: at most 8 GiB" echo "- Hard budget: 10 GiB" } >> "$GITHUB_STEP_SUMMARY" + # Going over budget evicts by last-access, so the entries that every + # PR depends on lose to bulk written by rarely-run workflows. Name the + # biggest consumers so the trade-off is visible instead of implicit. + { + echo + echo "
Largest cache entries" + echo + echo "| Size (GiB) | Last accessed | Key | Ref |" + echo "| ---: | --- | --- | --- |" + # `head` would close sort's stdout early and SIGPIPE it under + # pipefail, so cap the row count inside the same awk pass. + gh api --method GET "repos/$REPOSITORY/actions/caches" \ + -F per_page=100 --paginate \ + --jq '.actions_caches[] | [.size_in_bytes, .last_accessed_at, .key, .ref] | @tsv' \ + | sort -rn \ + | awk -F"$(printf '\t')" 'NR <= 15 \ + { printf "| %.2f | %s | `%s` | `%s` |\n", \ + $1 / 1073741824, substr($2, 1, 10), $3, $4 }' + echo + echo "
" + } >> "$GITHUB_STEP_SUMMARY" if (( bytes > 10737418240 )); then echo "::error::Actions cache usage is above the 10 GiB budget ($gib GiB)" exit 1 From 0a65d0f29dad82659c6a8ae05c3894d4c336f051 Mon Sep 17 00:00:00 2001 From: Ytallo Layon Date: Fri, 24 Jul 2026 17:36:52 -0300 Subject: [PATCH 2/2] test(harness): cap the E2E await deadline at 25s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `scenario_ms` covers only the await phase — stack boot is budgeted separately by `readiness_ms` — and the slowest passing scenario observed in CI takes ~4s. At 60s, a run with two wedged scenarios spent 122s of its 257s scenario step waiting on deadlines rather than doing work. 25s keeps roughly 6x headroom over the slowest observed pass while capping what a wedged scenario costs. The default-deadline test now also asserts the headroom, so a future reduction cannot quietly approach the real runtime. --- harness/tests/e2e/src/types/scenario.rs | 5 ++++- harness/tests/e2e/src/types/scenario/compiled.rs | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/harness/tests/e2e/src/types/scenario.rs b/harness/tests/e2e/src/types/scenario.rs index 32060527e..c6f52c806 100644 --- a/harness/tests/e2e/src/types/scenario.rs +++ b/harness/tests/e2e/src/types/scenario.rs @@ -14,7 +14,10 @@ mod tests { fn deadline_defaults_are_safe() { let timeouts = DeadlinesV1::default(); assert_eq!(timeouts.readiness_ms, 60_000); - assert_eq!(timeouts.scenario_ms, 60_000); + assert_eq!(timeouts.scenario_ms, 25_000); assert_eq!(timeouts.teardown_ms, 15_000); + // The await deadline must stay well clear of the slowest passing + // scenario (~4s in CI) so a slow runner does not read as a failure. + assert!(timeouts.scenario_ms >= 5 * 4_000); } } diff --git a/harness/tests/e2e/src/types/scenario/compiled.rs b/harness/tests/e2e/src/types/scenario/compiled.rs index 008e7cc4a..a3c1d0bf6 100644 --- a/harness/tests/e2e/src/types/scenario/compiled.rs +++ b/harness/tests/e2e/src/types/scenario/compiled.rs @@ -80,7 +80,12 @@ impl Default for DeadlinesV1 { fn default() -> Self { Self { readiness_ms: 60_000, - scenario_ms: 60_000, + // Stack boot is budgeted by `readiness_ms`; this covers only the + // await phase, where the slowest passing scenario observed in CI + // takes ~4s. 25s keeps ~6x headroom while capping what a wedged + // scenario costs — at 60s, a red run spent more time waiting on + // deadlines than doing work. + scenario_ms: 25_000, teardown_ms: 15_000, } }