Caching Toolchains Installed with mise
mise reinstalls every pinned tool on a fresh GitHub Actions runner. Cache the mise data directory, key it on the config files, and verify versions first.
mise installs the tools named in mise.toml or .tool-versions into a data directory under the home directory, and a GitHub Actions runner is destroyed when its job ends, so every run downloads and unpacks the whole toolchain again. Cache that data directory with WarpBuilds/mise-action@v2 and cache: true, key the entry on the configuration files that decide what gets installed, and print the resolved versions before the build step runs.
This guide covers where mise puts its files, the key that ties an entry to the toolchain configuration, a workflow that restores and verifies before it builds, and the minutes and dollars the change moves at real runner rates. For the wider question of carrying state between runs, see the guide to persistent caches for GitHub Actions runs.
Diagnosis
Read the duration GitHub prints next to the mise step on a recent run, then split that number into causes before editing anything.
The install work repeats on every job
mise resolves each pinned version against its backend, downloads an archive, extracts it into a versioned directory, and writes a shim so the binary appears on PATH. On a fresh runner none of that exists. Collect the working set from inside a job:
mise ls --current
du -sh ~/.local/share/mise
mise doctormise ls --current prints the active tools with the config file each version came from. du gives the size that a cache entry has to carry. mise doctor prints the resolved directories, which is the fastest way to confirm the paths on a runner image you did not build.
Three directories matter, and they are documented in the mise directories reference:
| Path | What it holds | Environment override |
|---|---|---|
~/.local/share/mise/installs | Extracted tool versions, one directory per tool and version | MISE_DATA_DIR |
~/.local/share/mise/shims | Shims that put the pinned versions on PATH | MISE_DATA_DIR |
~/.cache/mise (~/Library/Caches/mise on macOS) | Downloaded archives and metadata before extraction | MISE_CACHE_DIR |
The installs directory is the one worth carrying between runs. The download cache is a staging area for the same bytes, so caching both pays twice for one toolchain.
mise reads more than one configuration file
A repository can carry mise.toml at the root, .mise.toml next to it, and a .tool-versions file inherited from an earlier asdf setup, plus per-directory configs inside a monorepo. A cache key that hashes only the root file stays constant when a subdirectory bumps a version, so the entry restores, mise install notices the missing version, and the job pays a download that the key said it had avoided. Hash every file that can change the install set.
One key can serve two architectures
A WarpBuild cache entry is scoped to its key, its version, and its branch, and the version is a hash over the compression tool and the cached paths. Linux x64 and Linux ARM64 runners share an operating system and share ~/.local/share/mise/installs, so a key built from runner.os alone matches across both, and x64 binaries restore onto an ARM64 job. Put runner.arch in the key.
Part of the toolchain is already in the image
Check the versions your workflow pins against the preinstalled software on WarpBuild runners before optimizing the cache. When the image already ships the pinned version, mise installs nothing and the cache step is pure overhead. When the pins are far from the image, every job pays a full install, and a custom runner image is the cheaper place to put a large toolchain that changes twice a year.
Fix
Two changes, in this order.
1. Swap the action. WarpBuild maintains a fork of jdx/mise-action that installs mise, runs mise install for the tools in your config, and routes the toolchain cache through WarpBuild Cache. It takes the same inputs:
- uses: jdx/mise-action@v2
+ uses: WarpBuilds/mise-action@v2
with:
version: 2024.10.0
cache: truecache defaults to true, and on a WarpBuild runner the action uses WarpBuild Cache with no further configuration. Pin version so a mise release does not change resolution behavior in the middle of a week.
2. Verify the resolved versions before the build. A restored data directory and a bumped config can disagree, and the failure mode is a build against the wrong compiler rather than an error. One step closes it:
- name: Verify toolchain
run: |
mise ls --current
mise exec -- node --version
mise exec -- go versionThe step costs seconds and puts the active versions and their source files in the log, which is where you look first when a green branch starts failing on one runner size. If a config file lives outside the checkout, mise treats it as untrusted until mise trust records the decision.
The caching path differs on one of them. WarpBuild caching is documented as unsupported for Windows-based runners, so on a WarpBuild Windows runner use actions/cache@v4 against the data directory mise doctor reports, and keep WarpBuilds/mise-action@v2 on Linux and macOS.
Configuration
The manual form is worth having in the file when a monorepo needs an explicit path list or a shared key across workflows:
name: build
on:
pull_request:
push:
branches: [main]
jobs:
build:
runs-on: warp-ubuntu-latest-x64-4x
env:
MISE_DATA_DIR: ${{ github.workspace }}/.mise
steps:
- uses: actions/checkout@v5
- name: Restore mise toolchains
id: mise-cache
uses: WarpBuilds/cache@v1
with:
path: |
${{ github.workspace }}/.mise/installs
${{ github.workspace }}/.mise/shims
key: mise-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/mise.toml', '**/.tool-versions') }}
restore-keys: |
mise-${{ runner.os }}-${{ runner.arch }}-
- name: Install toolchains
run: mise install --yes
- name: Verify toolchain
run: |
mise ls --current
mise exec -- node --version
mise exec -- go version
- name: Build
run: mise exec -- make buildMISE_DATA_DIR pins the install path to the workspace, which keeps the cached path identical when part of the job runs in a container and part runs on the host. The cache client needs wget and zstd inside a container, and an entry saved with one compression tool does not restore where the other is in use.
Key design
Hash the configuration files and nothing else. hashFiles over **/mise.toml and **/.tool-versions changes when a pinned version changes and stays stable when application code changes, which is the behavior a toolchain cache wants. A commit SHA in the key produces a miss on every run and a new entry on every run, so storage bills for entries that are read once. The cache key term covers the general shape of a key and its fallbacks.
Keep the one restore-keys prefix. A partial hit is safe here: mise install compares the config against what is on disk and installs only the versions that are missing, so restoring last week's entry after a Node bump downloads one tool instead of six.
Expiry, fees, and larger toolchains
A cache entry expires 7 days after its last use and can be deleted at any time from the action or from the console, as the caching documentation describes. On hosted runners, cache storage bills at $0.20 per GB-month and each write or restore operation bills at $0.0001. On BYOC runners, cache storage and operations are included. Rates checked on the pricing page on 2026-08-13.
When the toolchain grows past the point where a transfer is worth it, move the state instead of the files. Snapshot runners capture the runner VM with the toolchain already installed, and they sit alongside remote Docker builders, CI observability, an MCP server, and the Action Debugger in the product surface.
Cost or Time Model
WarpBuild rates come from the pricing page and the cloud runners documentation, checked on 2026-08-13. The job below runs on warp-ubuntu-latest-x64-4x at $0.008 per minute.
Substitute your own numbers from the du and mise ls --current output collected in the diagnosis section.
- Six pinned tools: Node.js, Go, Python, Terraform, jq, and a linter, extracting to 1.4 GB in the installs directory and compressing to a 480 MB entry.
- 40 workflow runs per weekday over 22 weekdays, so 880 runs per month.
- Cold install, meaning download and extract all six tools: 2.40 minutes.
- Restored install, meaning entry download, decompression, and a no-op
mise install: 0.40 minutes. - A pinned version changes on one run in ten, so a save runs 88 times per month at 0.60 minutes, which amortizes to 0.06 minutes per run.
| Path | Toolchain minutes per job | Monthly runner minutes | Monthly cost |
|---|---|---|---|
| Cold install on every job | 2.40 | 2,112 | $16.90 |
| Restored from cache | 0.46 | 405 | $3.24 |
Add the cache fees on the restored path: 0.48 GB of storage at $0.20 per GB-month is $0.10, and 968 operations at $0.0001 each is $0.10. The restored path lands at $3.44 per month against $16.90, a difference of $13.46 on one job in one workflow. The number scales with the matrix: a five-leg matrix runs the install step five times per run, so the same change is worth five times as much before any runner resize enters the picture.
Two levers sit behind that arithmetic and are worth keeping separate when you build the case internally. The cache removes minutes, and the runner rate sets what each remaining minute costs. On this workload the cache is the larger lever, and it is the one you control entirely from the workflow file.
FAQ
Where does mise install tools on a GitHub Actions runner?
Into the mise data directory, which defaults to ~/.local/share/mise. Extracted versions land under installs/<tool>/<version> and the shims that put them on PATH land under shims. Downloaded archives are staged in the separate cache directory, ~/.cache/mise on Linux and ~/Library/Caches/mise on macOS. MISE_DATA_DIR and MISE_CACHE_DIR move both paths, and mise doctor prints the resolved values from inside a job.
What should the cache key hash for a mise toolchain?
The files that decide what gets installed, which are mise.toml and .tool-versions anywhere in the repository, so hashFiles('**/mise.toml', '**/.tool-versions'). Add runner.os and runner.arch, because a Linux x64 entry and a Linux ARM64 entry share the same operating system and the same cached paths and can otherwise match each other. Keep one shorter restore-keys prefix so a version bump restores yesterday's entry and mise install fills in the delta.
Does mise toolchain caching work on WarpBuild Windows runners?
No. The caching documentation lists Windows-based runners as unsupported for WarpBuild caching, so on a WarpBuild Windows runner use actions/cache@v4 against the data directory that mise doctor reports. Linux and macOS runners take WarpBuilds/mise-action@v2 with cache set to true, which needs no key management.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.