Repository navigation
GitHub Actions cache backend implements CacheBackend - #171
Merged
calvin-archastro merged 3 commits intoOct 21, 2026
Merged
calvin-archastro merged 3 commits into
calvin-archastro merged 3 commits into
Conversation
Contributor
Author
|
ArchDev is watching this pull request. State: settled (cycle 2 of 2 for head 377227b).
Paused by the |
Archdev-Job: job_db9bb37ac404accee701f54e Co-Authored-By: ArchDev <archdev@archastro.ai> Archdev-Model: anthropic/claude-opus-5.5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Epic: Aster restores real build artifacts from a shared cache, including the GitHub Actions cache
Task: GitHub Actions cache backend implements CacheBackend
Local git hooks skipped: pre-push on the submission push and the publish push. CI is the gate for what those hooks check.
Problem and author intent
Aster already had a GitHub Actions cache v2 Twirp client, but it was protocol-only.
ghawas not aCacheBackend: selection keptnoneandghalocal-only, and an unsetASTER_REMOTE_CACHEalways resolved to aster-srv. A job could not publish or restore content-addressed build artifacts through the Actions cache.A one-entry-per-blob mapping would also thrash that store. Keys are immutable, new entries are rate-limited, and the default repo quota is small with idle eviction. The intended design is one manifest receipt per target, one pack entry per run for blobs under 1 MiB, and a separate content-addressed entry for larger blobs, plus selection, an end-of-run flush, and a summary. Manifest schema stays compatible by adding an optional pack list.
What changed
Before: there was no GHA key layout, no pack membership on manifests, and no shared remote client across the per-target cache tiers the executor builds. Small outputs had nowhere to be bundled,
find_missingcould not be a lookup-only GHA operation, and runs printed no remote cache summary.After, on the later revision:
GhaBackendimplementsCacheBackendover the existing client. Manifest keys areaster-v<schema>-ac-<os>-<arch>-<taskkey>.put_blobzstd-compresses the payload and checks the digest. Uncompressed size of at least 1 MiB (PACK_THRESHOLD) is uploaded immediately asaster-v<schema>-cas-<digest>. Smaller frames are staged in memory.<digest>members ataster-v<schema>-pack-<os>-<arch>-<sha256 of the member digests>. The member set is sealed when the first manifest is stored; a small blob staged after that seal is its owncasentry.finish()uploads a non-empty stage and is a no-op when the stage is empty or writes have stopped.Manifestgains optionalpacks: Vec<PackRef>(serdedefault, omitted when empty). Older manifests still decode. Schema version is unchanged.put_manifestrecords a pack ref only for digests in the sealed member set, then uploads the manifest. It does not upload the pack first, despite thefinish()comment that says the pack is uploaded before a manifest so a reader never sees a dangling pack key.find_missingonly looks keys up. It never callsCreateCacheEntry. A create that reports the key already exists, or that has no upload URL, does not upload or finalize.RATE_LIMITS_BEFORE_STOP = 1) stops later remote writes for the process, logs one warning with the 429 count and entries created, and those writes return success without another request. Detection is a substring match on the error text. The same path runs for lookup failures, so a 429 on download also stops writes.ASTER_REMOTE_CACHE=ghaselects this backend whenGhaBackend::from_env()can build a client (ACTIONS_RESULTS_URLmust behttps://, plusACTIONS_RUNTIME_TOKEN). If either is missing, one warning is printed and the run stays local-only. If the variable is unset,GITHUB_ACTIONS=true, and bothACTIONS_RESULTS_URLandACTIONS_RUNTIME_TOKENare non-empty, selection isghainstead of aster-srv. Outside that runtime, unset still means aster-srv.OnceLock). The firstinstall_gha_backendwins so every tier shares the staged pack. The executor callsfinish()at end of run (failure is a warning) and printsaster cache summary: remote_hits=N remote_misses=N entries_created=N rate_limited=NviaCacheSummary'sDisplay. JSON execution output nests the same object atsummary.cache_summarywhen a GHA backend is installed; the field is omitted otherwise..github/workflows/ci.ymladds--test cache_ghato a named integration-test invocation. The job name is not in the hunk. Author notes say this is so the macOS job, which lists tests by name, does not skip the file.An earlier snapshot of the same base had the backend and selection, but not the workflow line,
CacheSummaryDisplay, the unset-on-Actions selection assertion, or the JSON pointer check.File-header comments still say the threshold is zstd frame size.
put_blobcompares uncompressed length. The large-blob test uses an incompressible payload over 1 MiB, so it does not distinguish those two rules.Scope
Later revision: 17 paths, 45 hunks, +1481/−27, including
.github/workflows/ci.yml. Earlier snapshot: 16 paths, 44 hunks, +1428/−27, same base, no workflow file. Supplied diffs report no omitted hunks or paths. The overall payload and check-log excerpts are marked truncated, so this is not a claim of evidence beyond those excerpts.In scope:
src/cache/gha/backend.rs, gha module exports,GhaCacheClient::for_test(http only for tests;from_envstill rejects non-https so a runtime token is not sent in plaintext outside tests), remote selection and the shared slot,PackRef, executor flush and summary, JSONcache_summary, mechanicalpacks: Vec::new()updates in existing manifest fixtures,tests/cache_gha.rs, and the CI list line.Out of scope: aster-srv and HTTP cache protocol behavior (fixture field only), a manifest schema bump, and changes to the Twirp retry budget.
Risk assessment
Medium. Packing is what keeps a many-target run from creating one Actions entry per small output, but blobs at least 1 MiB uncompressed, and small blobs staged after the first manifest seals the pack, each become their own entry. Quota and the ~200 new entries/min limit remain real.
Auto-select changes the default remote on GitHub Actions jobs that previously fell through to aster-srv whenever the runtime env is present. Explicit
ghawithout that env degrades to local-only instead of failing the build.Manifests can be published before the pack exists. A crash, or a
finish()failure that is only a warning, leaves receipts pointing at a missing key. After a 429, remote writes return success without uploading, so a rate-limited run can look green while new artifacts exist only locally.find_missingtreats a digest as present if this process has already remembered its pack. It does not re-fetch the pack. A cold process that has not loaded the manifest only looks up thecaskey, so a packed blob looks missing until the manifest is read.The pack is a hand-rolled ustar (member names must be ≤ 100 bytes; digest hex fits). Staged frames stay in memory until
finish(). The process-wide slot means the first GHA backend installed in the process is the one every later tier sees, including across tests in one process.User impact
On a GitHub Actions job with both cache runtime variables set, an unset
ASTER_REMOTE_CACHEnow uses the Actions cache instead of aster-srv. Set the variable explicitly to keep another backend.ASTER_REMOTE_CACHE=ghawithoutACTIONS_RESULTS_URLorACTIONS_RUNTIME_TOKENwarns once and uses only the local cache.Runs that installed the GHA backend print one summary line.
aster run --jsonincludessummary.cache_summary(remote_hits,remote_misses,entries_created,rate_limited) only in that case. Readers of older manifests are unaffected. Small outputs are restored from the pack key recorded on the manifest; large outputs use their owncasentry. After one surviving 429, further remote writes in that process are skipped.Testing
Canonical tests live in
tests/cache_gha.rsand talk to an in-process fake Twirp and blob server, not the live Actions API:upload_from_a_hits_from_b— workspace A uploads; workspace B with an empty local cache hits, sees a pack ref, and restores the blob, with nocasentry for that small blob. The downloader runs only afterfinish().existing_key_is_not_overwritten— a second store of the same manifest key leaves the first decoded blob in place. The create-count assert only checks the counter did not go backwards.repeated_429_stops_writes— creates that return 429 stop later creates and incrementrate_limited.fifty_small_outputs_bundle_into_one_pack— 50 small outputs stay at ≤ 51 entries with bundling on versus ≥ 100 with bundling off. Blobs are staged before any manifest so the pack is not sealed around one blob.large_blob_is_its_own_entry— an incompressible blob over 1 MiB is acasentry and does not open a pack.gha_selected_when_runtime_env_present— explicitghaselects a backend when the runtime env is https plus a token; unsetASTER_REMOTE_CACHEonGITHUB_ACTIONS=trueselectsgha; outside Actions, unset stays aster-srv. The rewritten URL is not expected to answer.summary_line_and_json_object— summary line prefix andsummary.cache_summaryJSON nesting viabuild_execution_output_with_cache.The CI workflow hunk adds
--test cache_ghato a named test list. Other jobs are not in the excerpt, so this does not show that every CI platform runs the file.Pipeline checks
format,lint, andtestare reported succeeded with exit code 0. Those excerpts are truncated and do not showcache_gharesults, so reported pipeline success is not evidence that the new tests ran. Author notes claim the canonical tests passed; that is advisory and not confirmed by the truncated logs. The patch is evidence of the test code, not of a green run. No live Actions round trip is in the supplied evidence.Follow-ups and known issues
finish()as a failed remote publish. Todayput_manifestuploads the receipt first.find_missingdoes not resolve pack membership from the remote unless this process has already read the manifest.Ok(()), so callers cannot tell uploaded from dropped.select_remoteorinstall_gha_backendin one process.write_ustar.