mise-action/.github
Chad McElligott 434d5feca5
fix(cache): isolate cache keys per working_directory in monorepos
Problem
-------
mise-action hashes ALL mise config files in the repo to compute a single
default cache key. In a monorepo with multiple projects (e.g., apps/frontend,
apps/backend), this causes cache pollution:

1. Job A runs for apps/frontend, installs only frontend tools
2. Cache is saved with a key based on ALL configs
3. Job B runs for apps/backend, gets cache HIT (same key)
4. Job B finds frontend tools but not backend tools
5. Job B has to install all tools because they are missing from cache

Additionally, any change to an unrelated project config would bust the
cache for all projects.

Solution
--------
When working_directory is set, compute the default cache key using only the
config files that affect that directory (detected via `mise config ls --json`)
instead of globbing all configs in the repo.

This required separating binary and tools caching:
- Binary cache: restored first so mise is available for `mise config ls`
- Tools cache: default key computed after mise is installed

Key Implementation Details
--------------------------
1. Cache separation:
   - restoreMiseBinaryCache/saveMiseBinaryCache for the mise binary
   - restoreToolsCache/saveToolsCache for the full mise directory
   - Binary cache key: `{prefix}-binary-{platform}-{version}-{dirHash}`
   - Tools default cache key: based on config file contents for working_directory

2. Binary backup during tools cache restore:
   The tools cache includes bin/, which could overwrite the binary that
   setupMise() just installed. We use withBinaryBackup() to backup the
   binary before restoring the tools cache and restore it afterward.

   An alternative approach would be to only cache installs/ and shims/
   instead of the full miseDir(), but that would change the caching
   behavior for existing users. Using withBinaryBackup() retains the
   original caching behavior while preventing the binary from being
   overwritten.

3. Binary cache key includes mise_dir hash:
   Prevents cache collision when users change mise_dir between runs.
   Without this, a cache hit could restore the binary to the wrong location.

4. Explicit mise binary path:
   Uses full path to mise binary instead of relying on PATH lookup,
   avoiding potential race conditions with core.addPath().

5. Lock file handling:
   - .toml files: look for corresponding .lock file
   - .tool-versions: look for mise.lock in the same directory

6. Graceful degradation:
   If `mise config ls` fails when working_directory is set, caching is
   disabled with a warning rather than:
   - Failing the action entirely, or
   - Falling back to glob patterns (which would reintroduce the bug)

Backward Compatibility
----------------------
- working_directory not set: No change, uses existing glob of all configs
- working_directory set: Default cache key based on `mise config ls` output

Note on cache_key input: The `cache_key` input now only controls the tools
cache key. The binary cache key is always computed automatically based on
platform, version, and mise_dir. This is generally better since the binary
cache is version-stable and does not need custom key logic.

Test Coverage
-------------
Added test-monorepo-cache.yml with 8 test scenarios:
- install-backend/restore-frontend: Verify cache isolation
- install-frontend/unrelated-change-no-bust: Verify unrelated changes do not bust cache
- parent-config-change: Verify parent config changes bust child cache
- lock-file-change: Verify lock file changes bust cache
- install-default-mise-dir/restore-custom-mise-dir: Verify mise_dir in cache key
2026-01-16 22:48:09 -06:00
..
linters feat: support windows (#122) 2024-09-25 21:27:52 +00:00
workflows fix(cache): isolate cache keys per working_directory in monorepos 2026-01-16 22:48:09 -06:00
renovate.json feat: use autofix.ci to auto-update dist/ on all PRs 2025-10-31 09:43:48 -05:00