Expand description
Persistent dependency and tool caches for native runners.
An ephemeral attempt starts in a fresh directory, and on Windows with a
fresh profile (runner_env::platform_defaults), so every tool cache a job
relies on starts empty: pnpm reinstalls its store, setup-node downloads
Node again into _work/_tool, NuGet restores every package from the
network. Worse, a cache under the attempt directory has a different absolute
path every job, and actions/cache hashes the paths into its key, so even an
explicit cache step never hits.
This module gives each runner a set of environment variables that point those tools at directories that outlive the attempt, without anything being configured by hand on each machine.
§Layout
<cache root>/ default <runner root>/_cache
.lock LockKind::DependencyCache
.usage.json the daemon's last measurement
.trash/ namespaces being deleted
<owner>/<repo>/ a repository policy's namespace (lower case)
_org/<org>/ an organization policy's, when it opts in
_shared/<name>/ a namespace policies share on purpose
.last-used touched at every launch; the LRU clock
npm/ pnpm-store/ nuget/ … caches every runner of the namespace shares
_slots/<n>.lease which attempt holds slot n
_slots/<n>/tool-cache/ … caches one runner at a time may writeOwners and organizations on GitHub cannot start with _, so _org and
_shared never collide with a repository’s namespace.
§Trust model
A cache is an input to the next job that reads it: a job that can write
npm/ can plant a package the next job installs. So the default namespace
is one repository: every profile of owner/repo shares one, and no
other repository can read or write it. An organization policy accepts jobs
from every repository in the organization, so it gets no cache unless an
operator opts in, for the same reason it never gets a persistent workspace.
Two policies share a namespace only when an operator names the same
_shared/<name> for both. None of this is a defence against a hostile
workflow in the same repository; that is the model GitHub documents for
self-hosted runners.
§Concurrency
Concurrent runners of one policy share a namespace. Every cache marked
Sharing::Namespace in TOOLS is documented as safe for concurrent
processes (cacache, the pnpm store, NuGet with a shared scratch folder, the
Go caches, Yarn Berry, uv, Playwright’s lock). The rest are
Sharing::Slot: each runner leases the lowest free slot of its namespace
and gets that slot’s own directory. The @actions/tool-cache library is the
important one: cacheDir deletes a version’s folder before copying into it,
with no lock, so two cold jobs filling the same Node version would delete
each other’s toolchain.
A lease is held while its attempt’s runtime still holds the runner package
(<runtime>/bin), which is removed by the attempt’s cleanup in both
workspace modes. A crashed daemon therefore keeps a lease for as long as the
attempt it belongs to is not cleaned, which is exactly as long as a runner
may still be using the slot.
§Disk
The daemon measures the root periodically and, over the cap, removes whole
namespaces in least-recently-used order, never one with a live lease. The
check is repeated under LockKind::DependencyCache immediately before
the namespace is renamed aside, and a launch takes its lease under the same
lock, so a prune cannot take a namespace a runner is about to use.
§Precedence
Platform defaults < these variables < runner.env < the per-attempt
TMPDIR/TEMP/TMP. A variable an operator sets in runner.env always
wins, and so does one the service’s own environment already carries (a
systemd drop-in, a launchd plist, a machine-wide variable on Windows): a
cache variable only fills in what nobody set ([without_inherited]).
Structs§
- Cache
Config caches.toml: what an operator changed from the defaults. Everything is optional; an absent file is every default.- Cache
Tool - One tool, or one family of variables that is switched on and off together.
- Cache
Usage - The whole root, as last measured.
- Cache
Variable - One variable a tool reads.
- Launch
Caches - What one launch gets.
- Namespace
Usage - One namespace’s size and age.
- Platforms
- Which platforms a variable is set on.
- Resolved
Root - The resolved cache root.
- Selection
- What a target gets.
- Target
Cache Config - One repository’s or organization’s settings.
- Tool
State - One tool’s state for a target.
Enums§
- Cache
Error - Why the configuration could not be used, or a cache operation failed.
- Namespace
- Whose cache a runner uses.
- Root
Source - Where the cache root came from.
- Sharing
- Whether concurrent runners of one namespace share a cache directory.
- Tool
Source - Where one tool’s on/off state came from.
- VarValue
- What a variable is set to.
Constants§
- CONFIG_
FILE - The configuration file’s name inside the configuration directory.
- DEFAULT_
MAX_ SIZE_ GIB - The default cap on the whole cache root.
- DEFAULT_
ROOT_ NAME - The default cache root’s name inside the host runner root.
- LOCK_
WAIT - How long a launch or a prune waits for
LockKind::DependencyCache. - ORGANIZATION_
OPT_ IN - The reason an organization policy has no cache until an operator opts in.
- PRUNE_
INTERVAL - How often the daemon measures and prunes.
- TOOLS
- Every cache this module knows. Adding a tool is one entry.
- USAGE_
FILE - The daemon’s last measurement, inside the cache root.
Functions§
- config_
path_ in - The
caches.tomlpath for a configuration directory. - environment
- The variables
toolsset onplatform, and the directories they name. - human_
bytes - A size for an operator:
1.5 GiB,320 MiB. - inherited
- Whether this process’s environment sets a variable named
name, compared without regard to case: npm readsnpm_config_*in any case, so the service’sNPM_CONFIG_CACHEand this module’snpm_config_cacheare one setting even on Unix, and the later one would win. - platform_
fallback_ root - The platform location used when the runner root has whitespace in it.
- prepare_
launch - Leases a slot in
selection’s namespace for the attempt atruntime, creates every directory its variables name, and marks the namespace used. - prune
- Measures
root, removes least-recently-used idle namespaces until it fits undermax_bytes, and records the result inUSAGE_FILE. - resolve_
root - The cache root for a host whose effective runner root is
runner_root. - runtime_
holds_ runner - Whether the attempt whose runtime is
runtimemay still be running a runner: its runner package is still there. Cleanup removes it in both workspace modes. - select
- What
targetgets onplatformunderconfig. Host-wide tool settings override the defaults and the target’s override both. - target_
key - The key a target’s settings are stored under.
- tool
- The tool named
id. - tool_
ids - Every tool id, in table order, for help text and errors.
- validate_
namespace_ name - A shared namespace name: a single, portable, lower-case path segment.
- validate_
root_ text - A cache root an operator may configure: absolute and free of whitespace.