Skip to content

fix(ci): improve caching - #11128

Merged
mergify[bot] merged 3 commits into
mainfrom
ci-improve-caching
Aug 6, 2026
Merged

fix(ci): improve caching#11128
mergify[bot] merged 3 commits into
mainfrom
ci-improve-caching

Conversation

@conradoplg

Copy link
Copy Markdown
Collaborator

Motivation

Closes #11127

Solution

See doc update in the diff

Tests

Specifications & References

Follow-up Work

AI Disclosure

  • No AI tools were used in this PR
  • AI tools were used: Claude wrote this and I haven't really checked thoroughly, but overall it seems to make sense.

PR Checklist

  • The PR title follows conventional commits format: type(scope): description
  • The PR follows the contribution guidelines.
  • This change was discussed in an issue or with the team beforehand.
  • The solution is tested.
  • The documentation and changelogs are up to date.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves CI cache efficiency by limiting writes, consolidating matrix caches, and disabling unnecessary caches.

Changes:

  • Restricts cache writes primarily to main.
  • Shares caches across crate and lint matrices.
  • Documents the caching policy.

The selected cache seed crates are not the widest dependency closures. Test evidence and completed AI disclosure are also missing.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
.github/workflows/tests-unit.yml Restricts unit-test cache writes.
.github/workflows/test-crates.yml Consolidates crate caches.
.github/workflows/lint.yml Optimizes lint-job caching.
.github/workflows/README.md Documents cache policy.
Suppressed comments (2)

.github/workflows/test-crates.yml:197

  • This seed is also not the widest eligible MSRV closure. zebra-utils directly depends on zebra-rpc and, under this job's --all-features --all-targets command, adds dependencies such as reqwest, full Tokio, and its utility targets. Selecting zebra-utils preserves the zebra-rpc dependency artifacts plus those additional artifacts instead of rebuilding them on each MSRV run.
          cache-save-if: ${{ github.ref == 'refs/heads/main' && matrix.crate == 'zebra-rpc' }}

.github/workflows/README.md:160

  • The documented seed is not the widest dependency closure: zebrad depends on zebra-rpc and zebra-utils, while zebra-utils itself depends on zebra-rpc. Document the actual widest eligible seed for each matrix so future cache changes do not preserve the less effective zebra-rpc choice.
2. **Wide matrices share one key.** The `test-crates.yml` matrices use `cache-shared-key` and seed
   the shared cache from a single leg (`zebra-rpc`, which has the widest dependency closure), rather
   than one ~1 GB near-duplicate cache per crate.

Comment thread .github/workflows/test-crates.yml Outdated
Comment thread .github/workflows/README.md Outdated
Comment thread .github/workflows/lint.yml Outdated
Comment thread .github/workflows/test-crates.yml Outdated

@alchemydc alchemydc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the mechanism against the pinned action and the vendored rust-cache source rather than just the diff. I found no functional bug — the design is sound and I'm not holding the PR for prose. Four small asks below, then the parts I checked so they don't get re-litigated.

Verified

  • All four inputs used here exist at the pinned 46268bd (cache, cache-key, cache-shared-key, cache-save-if); it wraps Swatinem/rust-cache@c193711 (v2.9.1).
  • rust-cache v2.9.1 src/config.ts: shared-key replaces key + GITHUB_JOB, and matrix values are never part of the key. So cache-key: crate-build-${{ matrix.crate }} really did mint 12 distinct keys, and cache-shared-key really does collapse them to one. The diagnosis in #11127 holds.
  • lint.yml, test-crates.yml and tests-unit.yml all have push: branches: [main], and each changes gate defaults to 'true' on push events — so refs/heads/main runs genuinely happen and no job is left permanently cacheless under the new cache-save-if. This was my main worry going in.
  • Seed legs can actually fire: the matrix is 12 crates reverse-sorted so zebrad is first, and zebra-rpc is present in build-msrv's matrix.
  • The exception list is accurate — check-no-git-dependencies and release-readiness are both A-release-PR-only, benchmarks.yml is workflow_dispatch + C-benchmark. deny really is 12 legs (4 checks × 3 feature sets). merge_group exists but native merge queue isn't enabled, so the non-matching gh-readonly-queue/* ref is harmless.
  • README TOC lists ## headings only, so the 5 → 6 renumber needs no TOC edit. CI-only change, so no CHANGELOG.md entry needed.

One under-sold win worth noting in the comment: cache-shared-key: clippy is close to a no-op for the key itself (rust-cache already derived v0-rust-clippy from GITHUB_JOB). The real fix is the save gate — previously both clippy legs raced to write that same key, so the narrower release leg could win and PRs would restore a cache missing the proptest-impl / lightwalletd-grpc-tests dependencies.

Requests

1. The README names the wrong seed crate. Rule 2 says the test-crates.yml matrices seed from "zebra-rpc, which has the widest dependency closure", but build seeds from zebrad and only build-msrv uses zebra-rpc. zebrad is the correct stable seed — so the code is right and the doc is wrong, which means a future maintainer following the doc would change the code to the worse option. (Copilot's review already tripped on this and suggested changing the seeds; please don't — see below.)

Suggested framing: the stable build matrix seeds from zebrad because it has the widest closure; build-msrv can't use zebrad because its build step is if: matrix.crate != 'zebrad', so that leg compiles nothing and would save an empty cache — hence zebra-rpc.

Related nit: the MSRV comment's "zebra-rpc pulls in the rest of the workspace" overstates it — zebra-rpc doesn't pull in zebra-utils. For the record on Copilot's zebra-utils suggestion: neither crate is a strict superset, because -p zebra-utils --all-features activates only zebra-rpc's default features, not all of them. The current choices are fine; only the prose needs a trim.

2. Narrow the policy wording in rule 1. "Every job that builds the workspace sets cache-save-if" isn't true — coverage.yml, book.yml, advisory.yml (5 jobs) and zcashd-compat-regtest.yml all build without it. And I don't think expanding this PR to cover them is the right fix: advisory.yml and zcashd-compat-regtest.yml are schedule + workflow_dispatch, and a manual dispatch can target any branch or tag, so they aren't effectively main-only either. Scoping the stated policy to high-volume workflows that run on pull requests is simpler and stays true.

3. Please drop the Copilot co-author trailer. fe96728's body carries Co-authored-by: Copilot Autofix powered by AI <...>, which AGENTS.md explicitly prohibits for AI tools. This isn't cosmetic: .github/mergify.yml sets merge_method: merge and the repo has allow_squash_merge: false, so the trailer lands in main's history verbatim rather than being collapsed away. Needs an amend + force-push.

4. PR metadata. The Tests section is empty and both AI-disclosure boxes are unchecked (the disclosure text ended up on the "No AI tools were used" line). Given the note that this wasn't thoroughly checked, some before/after cache evidence would carry most of the review weight here — see below.

Post-merge checks

The acceptance criteria in #11127 work as verification for this, with two corrections now that I've read the diff:

  • "A representative PR creates no new v0-rust-* entry under its merge ref" needs to exclude A-release and C-benchmark PRs, per this PR's own documented exceptions.
  • "No shared key has multiple writers" now holds for the four keys this PR touches — clippy, crate-build, crate-build-msrv, unit-tests-1.97.0-default-release-binaries. (lint.yml's msrv matrix is a single leg, so there's no residual race there.)

Also: gh api --paginate --jq applies the jq filter per page, so the README snapshot command groups page-by-page. --paginate --slurp | jq ... is the exhaustive form. Not blocking — the live inventory is well under one page.

Explicitly not asking for

Flagging these so they don't resurface: the repeat-push cache-warmth loss on Cargo.lock-changing PRs, fail-fast: false on the crate matrices, documenting the non-seed legs' artifact coverage, hard-coding the newly raised quota number (mutable admin state — the "10 GB default, admins can configure more" wording is more durable), and an extra sentence on PR-cache economics (the README already covers it). All optional at best.

Unrelated follow-up

AGENTS.md says "(PRs are squash-merged — the PR title becomes the commit message)", but squash merge is disabled at the repo level and Mergify uses merge_method: merge. I'll file that separately — it's the reason per-commit trailers matter.


Review assisted by Claude Code, then independently cross-reviewed with OpenAI Codex, which caught the co-author trailer and the workflow_dispatch hole in my original rule-1 analysis.

@alchemydc

Copy link
Copy Markdown
Contributor

The raised 25 GB quota is already exhausted

Snapshot taken just now (2026-08-05), after the quota was raised from 10 GB to 25 GB earlier today:

$ gh api repos/ZcashFoundation/zebra/actions/cache/usage
{"active_caches_size_in_bytes":26643191326,"active_caches_count":47}

26.64 GB against a 25 GB limit — already over, so LRU eviction is running right now. By ref:

Ref Entries Size
refs/pull/11126/merge 37 20.53 GB
refs/pull/10910/merge 9 5.49 GB
refs/heads/main 1 0.63 GB

Two pull requests own 26.02 of 26.64 GB. main retains exactly one entry — v0-rust-crate-build-zebra-chain-build-…, 601 MB — so the reusable cache that every PR restores from has been almost entirely destroyed. That is the mechanism described in #11127, caught in the act.

The 2.5× headroom increase bought nothing because a single PR can exceed the whole pool on its own. The fix has to be write discipline, not more space.

Why we're confident this PR fixes it

Grouping the PR-ref entries by key family maps them directly onto this PR's three rules:

Key family Entries Size Addressed by
v0-rust-crate-* (per-crate matrices) 33 19.59 GB rule 2 — cache-shared-key
v0-rust-check / unit / release / msrv / clippy 6 4.46 GB rule 1 — cache-save-if: main
v0-rust-fmt / no / deny / crates 4 0.70 GB rule 3 — cache: false

Three independent reasons the arithmetic works out:

  1. The bulk is per-crate matrix keys. 19.59 GB of one PR's 20.53 GB sits in v0-rust-crate-*. I verified against Swatinem/rust-cache@c193711 (src/config.ts) that shared-key replaces the key + GITHUB_JOB component and that matrix values are never part of the key — so cache-key: crate-build-${{ matrix.crate }} genuinely minted one entry per crate, and cache-shared-key genuinely collapses each matrix to one.
  2. Both offending refs are PR merge refs. cache-save-if: ${{ github.ref == 'refs/heads/main' }} stops PR-ref writes entirely while leaving restores intact, so those 46 entries stop being created. The documented exceptions (A-release-only jobs, C-benchmark) are the only PR-ref writers left, and they are infrequent.
  3. 0.70 GB is stored by jobs that compile nothingfmt, no-test-deps, deny, and the crate-matrix generator. cache: false removes that outright.

Together, a typical PR's cache footprint goes from ~20 GB to roughly zero, and main's entries stop being evicted — which is the only cache any other branch can read.

I also confirmed the write path actually functions after the change: lint.yml, test-crates.yml and tests-unit.yml all have push: branches: [main], and each changes gate defaults to 'true' on push events, so refs/heads/main runs really do occur and no job is left permanently cacheless.

Reproduce the snapshot with:

gh api 'repos/ZcashFoundation/zebra/actions/caches?per_page=100' --paginate --slurp \
  | jq -r '[.[].actions_caches[]] | group_by(.ref)
      | map({ref: .[0].ref, entries: length, gb: ((map(.size_in_bytes)|add)/1e9*100|round/100)})
      | sort_by(-.gb)[] | "\(.ref)  n=\(.entries)  \(.gb)GB"'

(Note --slurp cannot be combined with gh's built-in --jq; pipe to external jq instead, or the grouping runs per page.)


Data collected with Claude Code.

alchemydc
alchemydc previously approved these changes Aug 5, 2026

@alchemydc alchemydc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Mechanism verified against the pinned action and rust-cache source — no functional bug found. Supporting cache data in the comment above; the four items in my earlier review are non-blocking, except that I'd still like the Copilot co-author trailer on fe96728 dropped, since merge_method: merge preserves it into main (see #11206).

@mergify mergify Bot added the queued label Aug 5, 2026
@mergify

mergify Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-08-05 23:36 UTC · Rule: batched · triggered by rule move to any queue if GitHub Rulesets are satisfied
  • 🟠 Checks running · on draft merge queue: checking main (8e9ff3b) and #11128 together #11207
  • 🚫 Left the queue2026-08-06 00:03 UTC · at fe967282099649e2a0176174f4627e3e19288cf3

This pull request spent 27 minutes 17 seconds in the queue, with no time running CI.

Waiting for
  • current-datetime < 2026-08-05T19:05:20[UTC]
  • any of: [🛡 GitHub repository ruleset rule PR Requirements]
    • check-neutral = test-crates
    • check-skipped = test-crates
    • check-success = test-crates
  • any of: [🛡 GitHub repository ruleset rule PR Requirements]
    • check-neutral = unit-tests
    • check-skipped = unit-tests
    • check-success = unit-tests
All conditions
  • all of [🛡 Merge Protections rule ❄️ v6.3.0 will be released on 2026-08-06 [Scheduled Freeze]]:
    • current-datetime < 2026-08-05T19:05:20[UTC]
  • any of [🛡 GitHub repository ruleset rule PR Requirements]:
    • check-neutral = test-crates
    • check-skipped = test-crates
    • check-success = test-crates
  • any of [🛡 GitHub repository ruleset rule PR Requirements]:
    • check-neutral = unit-tests
    • check-skipped = unit-tests
    • check-success = unit-tests
  • #review-threads-unresolved = 0 [🛡 GitHub repository ruleset rule PR Requirements]
  • github-review-approved [🛡 GitHub branch protection]
  • github-review-approved [🛡 GitHub repository ruleset rule PR Requirements]
  • any of [🛡 GitHub repository ruleset rule PR Requirements]:
    • check-success = @github-actions/lint
    • check-neutral = @github-actions/lint
    • check-skipped = @github-actions/lint
  • any of [🛡 GitHub repository ruleset rule PR Requirements]:
    • check-success = @github-actions/pr-gate-result
    • check-neutral = @github-actions/pr-gate-result
    • check-skipped = @github-actions/pr-gate-result

Reason

The pull request #11128 has been manually updated

Requeued — the merge queue status continues in this comment ↓.

@alchemydc alchemydc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving after the force-push — my previous approval was auto-dismissed when history was rewritten.

All three code/doc asks landed:

  • Seed crate wording ✅ Rule 2 names zebrad for the stable matrix and zebra-rpc for MSRV, and the in-code comment explains why (zebrad's build step is skipped there, so that leg compiles nothing). Doc and code now agree.
  • Policy wording ✅ No longer claims every workspace-building job sets cache-save-if, so the workflows that legitimately don't no longer contradict it.
  • Copilot co-author trailer ✅ Gone — verified all three commit messages via the API.

Nothing regressed: lint.yml and tests-unit.yml are byte-identical to what I reviewed, and test-crates.yml changed only in the MSRV comment.

Minor, not worth a round-trip: naming the deliberate exceptions (check-no-git-dependencies, release-readiness, benchmarks.yml) was useful, and "(There are some minor exceptions to this rule.)" leaves the next auditor to rediscover them.

@mergify mergify Bot added the queued label Aug 6, 2026
@mergify

mergify Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-08-06 13:54 UTC · Rule: batched · triggered by rule move to any queue if GitHub Rulesets are satisfied
  • Checks failed · on draft merge queue: checking main (8e9ff3b) and #11128 together #11208
  • 🚫 Left the queue2026-08-06 13:55 UTC · at b1bac5fbe42e4bf34215c3d208ab3dbaa59f032b

This pull request spent 1 minute 31 seconds in the queue, including 49 seconds running CI.

Waiting for
  • current-datetime < 2026-08-05T19:05:20[UTC]
  • any of: [🛡 GitHub repository ruleset rule PR Requirements]
    • check-neutral = @github-actions/lint
    • check-skipped = @github-actions/lint
    • check-success = @github-actions/lint
  • any of: [🛡 GitHub repository ruleset rule PR Requirements]
    • check-neutral = test-crates
    • check-skipped = test-crates
    • check-success = test-crates
  • any of: [🛡 GitHub repository ruleset rule PR Requirements]
    • check-neutral = unit-tests
    • check-skipped = unit-tests
    • check-success = unit-tests
  • any of: [🛡 GitHub repository ruleset rule PR Requirements]
    • check-neutral = @github-actions/pr-gate-result
    • check-skipped = @github-actions/pr-gate-result
    • check-success = @github-actions/pr-gate-result
All conditions
  • all of [🛡 Merge Protections rule ❄️ v6.3.0 will be released on 2026-08-06 [Scheduled Freeze]]:
    • current-datetime < 2026-08-05T19:05:20[UTC]
  • any of [🛡 GitHub repository ruleset rule PR Requirements]:
    • check-neutral = @github-actions/lint
    • check-skipped = @github-actions/lint
    • check-success = @github-actions/lint
  • any of [🛡 GitHub repository ruleset rule PR Requirements]:
    • check-neutral = test-crates
    • check-skipped = test-crates
    • check-success = test-crates
  • any of [🛡 GitHub repository ruleset rule PR Requirements]:
    • check-neutral = unit-tests
    • check-skipped = unit-tests
    • check-success = unit-tests
  • any of [🛡 GitHub repository ruleset rule PR Requirements]:
    • check-neutral = @github-actions/pr-gate-result
    • check-skipped = @github-actions/pr-gate-result
    • check-success = @github-actions/pr-gate-result
  • #review-threads-unresolved = 0 [🛡 GitHub repository ruleset rule PR Requirements]
  • github-review-approved [🛡 GitHub branch protection]
  • github-review-approved [🛡 GitHub repository ruleset rule PR Requirements]

Reason

The merge conditions cannot be satisfied due to failing checks

  • @github-actions/pr-gate-result

Failing checks:

Hint

You may have to fix your CI before adding the pull request to the queue again.
If you update this pull request, to fix the CI, it will automatically be requeued once the queue conditions match again.
If you think this was a flaky issue instead, you can requeue the pull request, without updating it, by posting a @mergifyio queue comment.

Requeued — the merge queue status continues in this comment ↓.

@jvff
jvff force-pushed the ci-improve-caching branch from b1bac5f to 9fe499b Compare August 6, 2026 14:24
@mergify mergify Bot added queued and removed dequeued labels Aug 6, 2026
@mergify

mergify Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • Entered queue2026-08-06 14:52 UTC · Rule: batched · triggered by rule move to any queue if GitHub Rulesets are satisfied
  • Checks skipped · PR is already up-to-date
  • Merged2026-08-06 14:53 UTC · at 9fe499b651eab4c322cc8d3f4310b5fbf4594820 · merge

This pull request spent 1 minute 5 seconds in the queue, including 11 seconds running CI.

Required conditions to merge
  • #review-threads-unresolved = 0 [🛡 GitHub repository ruleset rule PR Requirements]
  • github-review-approved [🛡 GitHub branch protection]
  • github-review-approved [🛡 GitHub repository ruleset rule PR Requirements]
  • any of [🛡 GitHub repository ruleset rule PR Requirements]:
    • check-success = @github-actions/lint
    • check-neutral = @github-actions/lint
    • check-skipped = @github-actions/lint
  • any of [🛡 GitHub repository ruleset rule PR Requirements]:
    • check-success = test-crates
    • check-neutral = test-crates
    • check-skipped = test-crates
  • any of [🛡 GitHub repository ruleset rule PR Requirements]:
    • check-success = unit-tests
    • check-neutral = unit-tests
    • check-skipped = unit-tests
  • any of [🛡 GitHub repository ruleset rule PR Requirements]:
    • check-success = @github-actions/pr-gate-result
    • check-neutral = @github-actions/pr-gate-result
    • check-skipped = @github-actions/pr-gate-result

@mergify
mergify Bot merged commit 3dd9756 into main Aug 6, 2026
134 checks passed
@mergify
mergify Bot deleted the ci-improve-caching branch August 6, 2026 14:53
@mergify mergify Bot removed the queued label Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

devops: improve caching

3 participants