Skip to content

ci: share Rust caches by build class and save on failure#520

Merged
peer2f00l merged 3 commits into
devfrom
chore/rust-ci-cache-shared-keys
Jul 23, 2026
Merged

ci: share Rust caches by build class and save on failure#520
peer2f00l merged 3 commits into
devfrom
chore/rust-ci-cache-shared-keys

Conversation

@peer2f00l

@peer2f00l peer2f00l commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Problem

CI Rust compile times run ~25 min, with near-integration-tests timing out at 40 min. The cache config in .github/actions/rust/action.yml is the cause:

  • Per-job cache keys. shared-key: rust-${os}-${github.job} gives every job its own multi-GB Rust cache. Across all workflows that's ~11 near-identical caches — well past GitHub's 10 GB per-repo limit, which triggers LRU eviction. The least-frequently-run jobs (NEAR integration, coverage — both change-gated) are the first evicted, so they rebuild from cold nearly every run.
  • cache-on-failure: false. A job slow enough to hit its timeout-minutes is cancelled before rust-cache's post-step saves, so it can never seed a warm cache: cold build → timeout → still cold. Self-reinforcing.

Change

  • Add a cache-key build-class input to the rust composite action, falling back to the job id when unset.
  • Collapse the five host-target jobs (code-linter, binary-build, fast-tests, near-integration-tests, artifact-drift-check) plus gas-report onto one shared host cache. coverage (instrumented), soroban / feature-matrix (wasm / no_std) keep private keys via the fallback — their artifacts don't share.
  • Set cache-on-failure: true so a timing-out job still seeds its cache. Safe: CI runs CARGO_INCREMENTAL=0, so there's no incremental state to corrupt.

Net: distinct Rust caches drop ~11 → ~6, keeping the total under 10 GB so gated jobs stop getting evicted into cold rebuilds.

Validation

Reasoned from config, not from cache-hit logs — the real proof is this PR's own runs. Editing test.yml triggers the NEAR + soroban gates, so the suite exercises the change. Watch near-integration-tests cache restore/save and wall-clock across the first couple of runs (the very first run is expected to still be cold-ish while it re-seeds the new host key).

🤖 Generated with Claude Code


This change is Reviewable

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The Rust composite action now supports configurable dependency-cache grouping and optional sccache. Workflows assign host or contracts cache keys, centralize Rust build settings, and configure shared disk cleanup with coverage-specific cache isolation.

Changes

Rust CI configuration

Layer / File(s) Summary
Rust action caching and compiler setup
.github/actions/rust/action.yml
Adds cache-key and sccache inputs, conditionally configures compiler caching, and updates dependency-cache seeding and shared-key behavior.
Workflow cache and build configuration
.github/workflows/test.yml, .github/workflows/gas-report.yml
Assigns host or contracts cache keys to Rust jobs and centralizes Cargo, SQLX, and debug-profile settings across workflows.
Coverage runner cleanup and cache isolation
.github/actions/free-disk-space/action.yml, .github/workflows/test.yml
Adds standard and aggressive disk cleanup, enables aggressive cleanup for coverage, and disables sccache for coverage execution.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: carrion256

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/rust-ci-cache-shared-keys

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • LINEAR integration encountered authorization issues. Please disconnect and reconnect the integration in the CodeRabbit UI.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/actions/rust/action.yml:
- Line 43: Update the shared-key expression in the Rust cache action so
cache-key: host is partitioned by build class, preventing clippy, binary, test,
integration-test, artifact-check, and gas-reporting jobs from sharing one
immutable archive. Include the relevant build-class input or identifier in the
key while preserving the existing runner OS and cache-key components.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0c13ee8f-2800-4217-b2e8-4962e6f0094c

📥 Commits

Reviewing files that changed from the base of the PR and between 908a2c1 and 698fb27.

📒 Files selected for processing (3)
  • .github/actions/rust/action.yml
  • .github/workflows/gas-report.yml
  • .github/workflows/test.yml

Comment thread .github/actions/rust/action.yml Outdated
@peer2f00l

Copy link
Copy Markdown
Collaborator Author

Status: caching works; NEAR now red on a pre-existing dev regression (not this change)

What this PR does (working as intended)

  • Consolidated per-job Rust caches by build class (host, contracts) + cache-on-failure: true. Cuts the repo from ~11 caches to ~6, under GitHub's 10 GB cap, so infrequent jobs stop getting evicted into cold rebuilds.
  • near-integration-tests no longer times out: it went from cancelled-at-40m to completing in ~35m (host-side compile now cached; the full sandbox suite runs to the end).

Why NEAR is red anyway

Completing the suite for the first time unmasked 2 pre-existing failures on dev:

  • universal-account::migration::from_0_2_0
  • universal-account::migration::migrate_accepts_legacy_direct_payload

Root cause: #519 (ENG-481) added an all-or-nothing migration guard in common/src/versioned_state/macros.rs (require!(!needs_migration(), "state migration incomplete: stored version is still behind target")), but the universal-account migration tests still exercise stepwise migration (migrate(V0) 0→1 while target is 2) and were not updated. The guard reverts the partial migrate, so assert_success() fails.

This is broken on dev itself — invisible only because the NEAR job has been timing out (the ENG-508 problem this PR fixes). feature/eng-517-... (run N consecutive migrations in one call) looks like the intended fix.

Blocker

This PR can't go green until dev's migration tests are fixed (ENG-517 / a test update). That's a contract concern owned by ENG-481/ENG-517, not this CI change. Will rebase onto current dev once that lands.

Known follow-up (not blocking)

The wasm contract prebuild is still uncached (near-sdk recompiles ~20×) because rust-cache doesn't preserve cargo-near's build dir — NEAR fits in 40m but with ~5m headroom. Candidate: a shared/persistent CARGO_TARGET_DIR for the prebuild. Tracked separately.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c9325a7fbf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/test.yml Outdated
Comment on lines +226 to +227
- name: Free disk space
uses: ./.github/actions/free-disk-space

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Move the local action after checkout

This local action path now runs before actions/checkout, so ./.github/actions/free-disk-space is not present in $GITHUB_WORKSPACE when the runner tries to resolve it. The binary-build job will fail before reaching checkout, and the same ordering is repeated in fast-tests, near-integration-tests, and coverage, so the required gate can fail whenever these jobs run; move checkout before the local action or keep this cleanup inline.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot 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.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/actions/rust/action.yml:
- Line 56: Update the save-if condition in the Rust action to require the
workflow ref to be the repository’s default branch for push, schedule, and
merge_group events. Preserve the existing event gating while preventing pushes
from main/dev or other non-default branches from writing to the cache.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c0078aaf-4273-49ed-a396-fc51f66454e1

📥 Commits

Reviewing files that changed from the base of the PR and between 3bb74e7 and c9325a7.

📒 Files selected for processing (3)
  • .github/actions/free-disk-space/action.yml
  • .github/actions/rust/action.yml
  • .github/workflows/test.yml

Comment thread .github/actions/rust/action.yml Outdated
# caches; PR runs restore-only. This keeps feature branches from bloating
# the 10 GB budget or racing to write partial caches, and gives every PR a
# complete, dev-seeded cache to restore.
save-if: ${{ github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'merge_group' }}

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.

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

for workflow in $(fd -t f -e yml -e yaml .github/workflows); do
  if rg -q 'uses:\s*\./\.github/actions/rust' "$workflow"; then
    echo "===== $workflow: triggers ====="
    sed -n '1,140p' "$workflow"
    echo
  fi
done

Repository: Templar-Protocol/contracts

Length of output: 538


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "Workflow files:"
fd -t f . .github/workflows

echo
echo "Workflows referencing ./.github/actions/rust:"
for workflow in $(fd -t f . .github/workflows -e yml -e yaml); do
  if rg -q 'uses:\s*\./\.github/actions/rust' "$workflow"; then
    echo "===== $workflow ====="
    sed -n '1,180p' "$workflow"
    echo
  fi
done

Repository: Templar-Protocol/contracts

Length of output: 10650


Limit cache writes to the default branch

save-if currently allows any push, and this action is used from .github/workflows/documentation.yml on dev and .github/workflows/test.yml on main/dev, so non-default branches can still write to the cache. Gate this on the default branch if you want cache writes to stay centralized.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/actions/rust/action.yml at line 56, Update the save-if condition in
the Rust action to require the workflow ref to be the repository’s default
branch for push, schedule, and merge_group events. Preserve the existing event
gating while preventing pushes from main/dev or other non-default branches from
writing to the cache.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d34415440b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/actions/rust/action.yml Outdated
shell: bash
run: |
{
echo "SCCACHE_GHA_ENABLED=true"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Make PR sccache runs read-only

In pull_request runs, rust-cache is now restore-only via save-if, but this step enables sccache's GHA backend in its default read/write mode, so the same PR jobs still upload compiler outputs outside that guard. Because this action runs in the linter, build, fast-test, and NEAR jobs, feature branches can still consume the repo's GitHub cache quota and evict the default-branch host/contracts caches this change is trying to protect; set SCCACHE_GHA_RW_MODE=READ_ONLY or disable the GHA backend for non-seeding events.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99e1af07c9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/actions/rust/action.yml Outdated
# Group by build class via `cache-key`, not `github.job`. Do not put raw
# target/component strings here: rust-cache rejects keys with commas, and
# comma-separated target lists are common in this repo.
shared-key: "rust-${{ runner.os }}-${{ inputs.cache-key || github.job }}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Let only a complete host job save the shared cache

Because save-if is true on default-branch/merge-queue runs and several jobs pass cache-key: host, this shared-key makes artifact-drift-check, binary-build, code-linter, and fast-tests all write the same exact rust-cache key. The GitHub cache docs state an existing cache cannot be changed, so if the small artifact-drift or binary job finishes first after a lockfile/key change, it seeds a partial host cache and the later test/clippy jobs restore an exact hit instead of replacing it with their fuller dependency artifacts; PRs then keep rebuilding test-only deps, undermining the timeout/cache fix. Make only the intended complete host producer save this key, or give producers distinct save keys with a shared restore prefix.

Useful? React with 👍 / 👎.

Comment on lines +232 to +234
if !reproducible {
command.arg("--no-abi");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep an ABI-generating contract build in CI

The cargo-near v0.19.2 README describes cargo near build as building the contract along with its ABI, and the near-sdk README calls --no-abi a way to skip schema/ABI errors. This non-reproducible branch is used by spawn_artifact_build for script/prebuild-test-contracts.sh, which feeds both just test-sandbox and script/gas-report.sh, so the required CI paths no longer exercise ABI/JsonSchema generation at all; a contract-facing type can pass tests but fail the release/reproducible build. Keep at least one required cargo-near build without this flag.

Useful? React with 👍 / 👎.

@peer2f00l

Copy link
Copy Markdown
Collaborator Author

Final state — node suite <40 min, caching fixed, workflows cleaned up

Reworked down to a small diff (+78/−78, 4 files) after measuring several approaches and reverting the ones that didn't pay off.

Root cause of the 40 min timeout

near-integration-tests had cache-on-failure: false, so when it timed out the post-step never ran and it never seeded a cache — cold → timeout → still cold, permanently. Plus the cold prebuild was ~24 min on its own.

What shipped

  • cache-on-failure: true — the node-integration cache (1.56 GB) now saves even when the job fails/times out. Verified: warm rerun restored it full match: true.
  • --no-abi on the test prebuild — cargo-near ran a separate ABI-generation build per contract that nothing in the suite reads (tests only load/deploy the .wasm). Measured ~7.3 min / 35% of the contract compile. Release blobs keep their ABI.
  • Cleanup — 4 copy-pasted "free disk space" blocks → one free-disk-space composite action; repeated env vars → one workflow-level env block.

Measured results (NEAR Integration)

run prebuild total
cold, original (+ABI) ~24 min timed out at 40
cold, --no-abi 18 min 39 min
warm, --no-abi 9 min 30 min

Caching demonstrably works: cold run seeds all per-job caches (including the failing NEAR job), warm rerun restores them and cuts the prebuild in half.

Tried and reverted (with data)

  • sccache: ~8% on the node suite — cdylib contracts + proc-macros don't cache, and the GHA backend threw ~1000 write-errors (throttling). Would need an external cache backend.
  • Sharding: works on wall-clock but triples billed runner-minutes (the prebuild can't be sharded), so it's a net cost increase.
  • Cache grouping / cache-factory: grouping poisons clippy-vs-build caches; restore-only-on-PR gave PRs zero cache benefit.

Still red (out of scope)

The two universal-account migration tests fail on the #519/ENG-517 guard-vs-stepwise conflict — fixed by the ENG-517 branch, not here. Every other job is green.

peer2f00l and others added 3 commits July 23, 2026 14:08
- Replace four copy-pasted "free up disk space" blocks with one
  `free-disk-space` composite action (with an `aggressive` input for coverage).
  It runs after checkout, since a local action needs the repo present.
- Hoist the env vars repeated across jobs (CARGO_TERM_COLOR, CARGO_INCREMENTAL,
  SQLX_OFFLINE, CARGO_PROFILE_{DEV,TEST}_DEBUG) into one workflow-level `env`
  block; jobs keep only what's job-specific.
- Raise the node-integration timeout to 45 min. It's a ceiling, not billed time
  (~30 min warm / ~39 min cold), so the headroom just avoids a cold-run variance
  kill wasting a whole run.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The node-integration job had `cache-on-failure: false` and timed out before
rust-cache's post-step ran, so it never seeded a cache: cold build -> timeout
-> still cold, permanently. Set `cache-on-failure: true` so it seeds even on a
failing/timed-out run. Safe because CI runs CARGO_INCREMENTAL=0, so there is no
incremental state to leave corrupt. Caching stays on per-job keys + save on
same-repo PRs (rust-cache defaults), so each cache matches its job's artifact
kind and a PR's later runs restore its earlier compilation.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Pass `--no-abi` to the `cargo near build` used for test artifacts. cargo-near
otherwise runs a separate ABI-generation build per contract — measured at
~7.3 min (35%) of the ~20.6 min contract compile — and nothing in the suite
reads the embedded ABI (tests only load and deploy the `.wasm`). This cuts the
cold prebuild ~24 -> ~18 min, bringing the node suite under 40 min. Reproducible
release blobs (`res/near`) keep their ABI.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@peer2f00l
peer2f00l force-pushed the chore/rust-ci-cache-shared-keys branch from 43b6cf1 to 5395027 Compare July 23, 2026 13:09
@peer2f00l
peer2f00l merged commit 8bca6af into dev Jul 23, 2026
50 of 52 checks passed
@peer2f00l
peer2f00l deleted the chore/rust-ci-cache-shared-keys branch July 23, 2026 14:46
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.

1 participant