Skip to main content

Module dependency_cache

Module dependency_cache 

Source
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 write

Owners 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§

CacheConfig
caches.toml: what an operator changed from the defaults. Everything is optional; an absent file is every default.
CacheTool
One tool, or one family of variables that is switched on and off together.
CacheUsage
The whole root, as last measured.
CacheVariable
One variable a tool reads.
LaunchCaches
What one launch gets.
NamespaceUsage
One namespace’s size and age.
Platforms
Which platforms a variable is set on.
ResolvedRoot
The resolved cache root.
Selection
What a target gets.
TargetCacheConfig
One repository’s or organization’s settings.
ToolState
One tool’s state for a target.

Enums§

CacheError
Why the configuration could not be used, or a cache operation failed.
Namespace
Whose cache a runner uses.
RootSource
Where the cache root came from.
Sharing
Whether concurrent runners of one namespace share a cache directory.
ToolSource
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.toml path for a configuration directory.
environment
The variables tools set on platform, 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 reads npm_config_* in any case, so the service’s NPM_CONFIG_CACHE and this module’s npm_config_cache are 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 at runtime, creates every directory its variables name, and marks the namespace used.
prune
Measures root, removes least-recently-used idle namespaces until it fits under max_bytes, and records the result in USAGE_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 runtime may still be running a runner: its runner package is still there. Cleanup removes it in both workspace modes.
select
What target gets on platform under config. 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.