fix: include runner image in cache key to prevent cross-provider collisions

The cache key included only os and arch (`mise-v1-macos-arm64-<hash>`),
so any repo running CI on multiple runner providers with the same
os/arch — github-hosted, namespace.so, BuildJet, self-hosted M-series
macs — would clobber each other's caches. The first run on a new
provider would restore tool installs built for the previous provider's
image, leading to "does not have an executable named X" errors or
SIGILL crashes on cached binaries built against a different glibc.

Append the GitHub Actions hosted-runner ImageOS env var (e.g.
"macos15", "ubuntu24") to the cache key's platform segment. Other
runners pool under "self-hosted"; users running multiple self-hosted
profiles can scope further with cache_key_prefix.

Implementation note: `getTarget()` is also used to construct the mise
binary download URL (must match the release-asset filename, e.g.
`mise-v2026.4.0-macos-arm64.tar.gz`). Adding the image suffix there
would 404 the download. So `getTarget()` keeps its current shape and
a new `getRunnerImageId()` helper composes the image discriminator
into the cache-key platform segment only.

This is a one-time cache miss for existing users; the cache rebuilds
on the next run and stays scoped per-image after that.
This commit is contained in:
jdx 2026-04-30 09:07:02 -05:00
parent ac8a6414ec
commit ef1bd0e351
No known key found for this signature in database
GPG key ID: 584DADE86724B407
4 changed files with 29 additions and 13 deletions

View file

@ -68,7 +68,7 @@ When using `cache_key`, you can use template variables to reference internal val
Available template variables:
- `{{version}}` - The mise version (from the `version` input)
- `{{cache_key_prefix}}` - The cache key prefix (from `cache_key_prefix` input or default)
- `{{platform}}` - The target platform (e.g., "linux-x64", "macos-arm64")
- `{{platform}}` - The target platform, including the runner image (e.g., "linux-x64-ubuntu24", "macos-arm64-macos15", "linux-x64-self-hosted"). The trailing segment is `process.env.ImageOS` on github-hosted runners and falls back to `"self-hosted"` elsewhere — preventing cache collisions when the same repo runs on different runner providers (github-hosted, namespace.so, self-hosted).
- `{{file_hash}}` - Hash of all mise configuration files
- `{{mise_env}}` - The MISE_ENV environment variable value
- `{{install_args_hash}}` - SHA256 hash of the sorted tools from install args

18
dist/index.js generated vendored
View file

@ -86042,10 +86042,7 @@ async function saveCache(cacheKey) {
});
}
async function getTarget() {
let { arch } = process;
// quick overwrite to abide by release format
if (arch === 'arm')
arch = 'armv7';
const arch = process.arch === 'arm' ? 'armv7' : process.arch;
switch (process.platform) {
case 'darwin':
return `macos-${arch}`;
@ -86057,13 +86054,24 @@ async function getTarget() {
throw new Error(`Unsupported platform ${process.platform}`);
}
}
/**
* Identifies the runner image so cached binaries from one provider
* (github-hosted, namespace.so, BuildJet, self-hosted) aren't restored
* onto another provider's image where their compiled-in paths and libc
* versions don't match. GitHub-hosted images export `ImageOS`
* (e.g. "macos15", "ubuntu24"); other runners leave it unset and pool
* under "self-hosted".
*/
function getRunnerImageId() {
return process.env.ImageOS || 'self-hosted';
}
async function processCacheKeyTemplate(template) {
// Get all available variables
const version = getInput('version');
const installArgs = getInput('install_args');
const cacheKeyPrefix = getInput('cache_key_prefix') || 'mise-v1';
const miseEnv = process.env.MISE_ENV?.replace(/,/g, '-');
const platform = await getTarget();
const platform = `${await getTarget()}-${getRunnerImageId()}`;
// Calculate file hash
const fileHash = await hashFiles(MISE_CONFIG_FILE_PATTERNS.join('\n'));
// Calculate install args hash

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View file

@ -483,11 +483,7 @@ async function saveCache(cacheKey: string): Promise<void> {
}
async function getTarget(): Promise<string> {
let { arch } = process
// quick overwrite to abide by release format
if (arch === 'arm') arch = 'armv7' as NodeJS.Architecture
const arch = process.arch === 'arm' ? 'armv7' : process.arch
switch (process.platform) {
case 'darwin':
return `macos-${arch}`
@ -500,13 +496,25 @@ async function getTarget(): Promise<string> {
}
}
/**
* Identifies the runner image so cached binaries from one provider
* (github-hosted, namespace.so, BuildJet, self-hosted) aren't restored
* onto another provider's image where their compiled-in paths and libc
* versions don't match. GitHub-hosted images export `ImageOS`
* (e.g. "macos15", "ubuntu24"); other runners leave it unset and pool
* under "self-hosted".
*/
function getRunnerImageId(): string {
return process.env.ImageOS || 'self-hosted'
}
async function processCacheKeyTemplate(template: string): Promise<string> {
// Get all available variables
const version = core.getInput('version')
const installArgs = core.getInput('install_args')
const cacheKeyPrefix = core.getInput('cache_key_prefix') || 'mise-v1'
const miseEnv = process.env.MISE_ENV?.replace(/,/g, '-')
const platform = await getTarget()
const platform = `${await getTarget()}-${getRunnerImageId()}`
// Calculate file hash
const fileHash = await glob.hashFiles(MISE_CONFIG_FILE_PATTERNS.join('\n'))