Task caching
Use ordinary sources and outputs checks to skip work that is already up to date. Enable experimental artifact caching when you also need to reuse successful results after switching inputs or deleting build outputs.
| Mechanism | Compares | On a hit |
|---|---|---|
| Freshness checks | Source and output modification times | Leaves existing outputs in place and skips the task. |
| Artifact cache | Declared input contents and other cache-key material | Restores declared outputs and replays captured logs. |
For freshness configuration, see sources and outputs. The remainder of this guide covers the experimental artifact cache.
Enable artifact caching
Stores successful task results in a content-addressed local cache and reuses them when the same task inputs are seen again. Declared filesystem outputs are restored after deletion. Tasks with outputs = [] cache their successful result and logs without storing filesystem artifacts, which is useful for checks such as linting, testing, and type checking. Declaring outputs = [] asserts that the task has no filesystem side effects that a cache hit needs to reproduce.
Artifact caching requires experimental, at least one matching source, and either explicit output paths or outputs = []. outputs = { auto = true }, absolute outputs, and output patterns (including the body of an exclusion) that escape the task directory are not supported.
[settings]
experimental = true
[tasks.build]
run = "npm run build"
sources = ["package.json", "src/**"]
outputs = ["dist"]
cache = { enabled = true, env = ["NODE_ENV"] }Commands listed in cache.command_inputs run before cache lookup. Their command text, stdout, and stderr are included in the cache key. Commands use the same inline shell (including a CLI --shell override), resolved environment and tools, working directory, and sandbox policy as the task. This is useful when inputs such as compiler versions or generated configuration cannot be represented by source files alone.
[tasks.build]
run = "npm run build"
sources = ["package.json", "src/**"]
outputs = ["dist"]
cache = { enabled = true, command_inputs = ["node --version", "npm config get registry"] }A command input must be non-empty and exit successfully. Its output is hashed without being printed or retained. Command inputs inherit the task timeout, or have a 30-second timeout when the task has none, and may emit at most 16 MiB across stdout and stderr. They should be fast, deterministic, and free of side effects because they run whenever mise computes the task's cache key. Command inputs are not run during dry runs or when caching is disabled for raw or interactive execution.
Set cache.audit = true to diagnose incomplete cache declarations on Linux. When a task executes, mise uses strace to report reads beneath the workspace root that do not match sources and writes beneath the task directory that do not match outputs. The audit is advisory and does not block the task or prevent a successful result from being cached. Access outside those roots and directory metadata reads are ignored to keep system libraries, executables, and path traversal out of the report.
Reported paths are always relative to the task directory, using .. for the paths above it that a read may legitimately touch. A reported read can be added to sources exactly as it was printed.
Audit mode requires strace on PATH. mise warns and runs the task normally when tracing is not available; other platforms are not currently supported. Cached tasks are not executed and therefore produce no audit report, so use mise run --force <task> when checking an existing cache entry.
Console warnings are limited to the first 20 paths per task, which is not enough to classify a task that reads thousands of undeclared files. Set task.cache.audit_report to also write every undeclared path as JSON Lines, one {"task", "kind", "path"} object per entry. Truncation happens once per mise invocation: the first audited task in each invocation truncates the file and later audited tasks in that invocation append to it, so one file holds that run's report for every audited task and a later run replaces it rather than adding to it.
MISE_TASK_CACHE_AUDIT_REPORT=audit.jsonl mise run --force build[tasks.build]
run = "npm run build"
sources = ["package.json", "src/**"]
outputs = ["dist"]
cache = { enabled = true, audit = true }External dependencies and lockfiles
Declare dependency manifests and lockfiles as filesystem inputs so dependency updates invalidate the cache. They can be listed directly in a task's sources, shared through an input group, or applied to every task in a config scope with task_config.global_inputs.
[settings]
experimental = true
[task_config]
global_inputs = ["@group:node-dependencies"]
[task_config.input_groups]
node-dependencies = ["package.json", "pnpm-lock.yaml"]
[tasks.build]
run = "pnpm build"
sources = ["src/**"]
outputs = ["dist"]
cache = { enabled = true }The lockfile content represents the resolved external dependency graph, so installed dependency directories such as node_modules generally should not be included. Resolved mise tools already participate in the cache key. Use cache.command_inputs for relevant external state that is not captured in committed files, such as a package registry selection or a compiler wrapper version:
[tasks.build]
run = "pnpm build"
sources = ["package.json", "pnpm-lock.yaml", "src/**"]
outputs = ["dist"]
cache = { enabled = true, command_inputs = ["pnpm config get registry"] }Only declare deterministic external state that can affect task outputs. Secrets and credentials should use pass-through environment variables instead so their values are not included in cache keys.
Per-run cache access
Use mise run --task-cache <mode> or MISE_TASK_CACHE to control task output cache reads and writes for one run:
read-writeuses cached results and publishes new results. This is the default.read-onlyuses cached results but does not publish misses.write-onlypublishes results but always executes instead of restoring.offdisables task output caching and uses ordinary source/output freshness checks.local-onlyreads and writes only the local cache, bypassing any configured remote service.
# Prevent an untrusted pull request from publishing cache entries
mise run --task-cache read-only test
# Warm the local cache without consuming existing entries
mise run --task-cache write-only build
# Diagnose a task without reading or writing task output artifacts
mise run --task-cache off --force buildThese modes only affect the experimental task output cache configured by a task's cache property. The existing --no-cache option controls fetching remote task definitions instead.
Remote cache and sensitive data
Configure the experimental remote build-cache service with task.cache.remote_url and a non-empty task.cache.remote_namespace. The namespace is an opaque repository or organization identifier; the server must isolate entries by both namespace and cache key. It is routing metadata, not an authentication mechanism or secret. Use a distinct namespace wherever writers should not be able to influence one another's cache entries.
[settings]
experimental = true
task.cache.remote_url = "https://cache.example.com/mise/"
task.cache.remote_namespace = "acme/widgets"
task.cache.remote_mode = "read-write"The client permits remote writes only for recognized protected-branch push jobs in GitHub Actions or GitLab. Local runs, pull requests, tags, and unrecognized CI contexts are restricted to reads; a write-only configuration disables the remote in those contexts. The server must enforce authorization independently. See the remote protocol.
Set MISE_TASK_CACHE_REMOTE_TOKEN in the process environment to send a bearer credential. The equivalent task.cache.remote_token setting is global-only, but the environment variable is preferred so a token does not need to be written to disk. mise redacts the token from settings trace output and marks its HTTP header as sensitive. Requests carrying credentials require HTTPS outside loopback. An unauthenticated HTTP connection is permitted with a warning and provides no transport confidentiality or server authentication. Servers should still use short-lived, least-privilege credentials, restrict namespace access, avoid logging authorization headers, and encrypt or otherwise protect stored cache objects according to their sensitivity and retention requirements.
For rotating credentials, set MISE_TASK_CACHE_REMOTE_TOKEN_FILE to a file containing only the bearer token. mise rereads the file before every request, which supports Kubernetes-projected service account tokens without restarting a long-running process. The equivalent task.cache.remote_token_file setting is global-only.
In GitHub Actions, mise can acquire and refresh a short-lived OIDC token itself. Grant the workflow permission to request an identity token and set its audience explicitly:
permissions:
contents: read
id-token: write
jobs:
test:
runs-on: ubuntu-latest
env:
MISE_TASK_CACHE_REMOTE_OIDC_AUDIENCE: https://cache.example.com
steps:
- uses: actions/checkout@v5
- uses: jdx/mise-action@v3
- run: mise run testThe cache service must trust GitHub's issuer, accept the configured audience, and authorize the workflow's identity claims for the selected namespace. mise obtains the token from GitHub's job OIDC endpoint, keeps it only in memory, and refreshes it before expiry. The audience setting is global-only, and acquisition fails with a clear error when the workflow lacks id-token: write permission.
Credential precedence is explicit token, token file, then automatic OIDC. This lets an emergency static credential override workload identity without changing project configuration. Other CI providers can supply their issued OIDC token directly through MISE_TASK_CACHE_REMOTE_TOKEN; they do not need a protocol-specific integration.
Task cache entries are not secret-free metadata. They contain captured stdout and stderr plus every declared output file. mise applies its configured output redactions before storing logs, but this is not a general secret scanner: a task can print an unknown credential or write one into an output artifact. Do not cache such a task unless those values are safe to retain and share with every reader of its local and remote cache. Clearing a local entry does not delete copies already uploaded to a remote service; use the remote service's retention and deletion controls as well.
Artifact checksums detect corruption and HTTPS authenticates the configured server in transit, but a checksum is not a signature from the original task runner. Any principal allowed to write a namespace can publish entries that its readers will trust. Give untrusted pull-request jobs read-only credentials or no remote credentials, use --task-cache read-only to prevent publishing, and isolate less-trusted writers in a separate namespace.
Cache correctness and deterministic tasks
Enabling cache is a correctness assertion: identical cache-key material must produce equivalent captured logs and declared outputs. Every value that can change the result must be represented by a source or input group, a resolved mise tool, cache.env, cache.command_inputs, or a cacheable dependency's artifact key. This includes configuration and lockfiles, locale or feature flags, compiler wrappers, generated inputs, and relevant external service state. Operating system and architecture are included automatically; other machine state is not.
Cache-enabled tasks should be deterministic and should not depend on undeclared files, wall-clock time, randomness, mutable network responses, or ambient environment variables. If such an input cannot be captured reliably, disable caching for the task. Pass-through environment variables are intentionally absent from the key and therefore must not influence cached logs or outputs. A task that uses credentials only to fetch content must key on a stable digest or lockfile for that content, not on the credential itself.
Declared outputs must completely describe the filesystem state that a hit needs to reproduce. Side effects outside those paths—database writes, deployments, notifications, and changes elsewhere in the workspace—are not replayed. outputs = [] is only correct when no filesystem side effect needs to be reproduced. On Linux, cache.audit = true can reveal many undeclared workspace reads and writes, but the audit is advisory and cannot prove determinism or observe every external dependency.
When correctness is uncertain, use --task-cache off while diagnosing, add missing key inputs, and force an uncached execution before trusting new entries. Use separate remote namespaces when a change to task semantics or undeclared external state could otherwise collide with entries produced under a different trust policy.
[tasks.lint]
run = "eslint ."
sources = ["package.json", "src/**"]
outputs = []
cache = { enabled = true }To enable caching by default for every eligible task in a config scope, set task_config.cache. Only tasks with at least one source and either explicit output paths or outputs = [] inherit this default; other tasks remain uncached. A task-local cache value overrides the scoped default.
[settings]
experimental = true
[task_config.cache]
enabled = true
env = ["NODE_ENV"]
command_inputs = ["node --version"]
[tasks.build]
run = "npm run build"
sources = ["package.json", "src/**"]
outputs = ["dist"]
[tasks.deploy]
run = "./deploy.sh"
cache = { enabled = false }The cache key includes source contents, the task definition and arguments, resolved task environment, the values (or absence) of variables named in cache.env, command-input output, resolved tool versions, dependency artifact keys, and the operating system and architecture. Variables inherited from the ambient process are ignored unless listed in cache.env.
Inspect and diagnose cached results
Use mise run --task-cache-explain <task> to print a deterministic breakdown of the inputs that produced the cache key without printing the aggregate key itself. Environment variables are identified only by name and whether they are set, while mise variables are identified only by name, so the explanation does not publish their contents or per-value digests. Other potentially secret-derived inputs—including source contents, dependency keys, command output, task definitions, and resolved tool versions—are reported only by category and count. Matched source paths, declared output patterns, currently resolved output roots, and the target platform are listed directly.
Combine the flag with --dry-run to inspect the key inputs without executing, restoring, or storing the task. Cache command inputs still run when the explanation is explicitly requested because their output hashes are part of the key.
Use mise run --dry-run --task-cache-explain-json <task> for machine-readable diagnostics. The command writes one compact JSON object per selected task to stdout, using the same redaction rules as the human explanation. Each object includes the opaque cache_key so consumers can distinguish separate invocations of the same task without exposing their arguments or dependency environment values. This JSON Lines format remains streamable when a pattern selects multiple tasks. Cache command inputs still run so their presence can be reported accurately, but their output and hashes are not included.
Use mise run --task-cache-stats <task> to print a run summary with the number and percentage of artifact cache hits, the uncompressed output and log bytes restored, and the execution time recorded when each restored entry was created. Entries written before this metadata was added remain readable and contribute zero bytes and time when restored. Freshness skips that do not perform a cache lookup are not counted as hits or misses.
Use mise cache task <task> to inspect every local output-cache entry associated with a configured task. The table shows each key, whether it is the current freshness entry, its stored and restorable sizes, recorded execution time, last access time, and output roots. Add --json for structured output as an array, including when only one task matches. Entries created before task identity metadata was added can be inspected when they are the task's current entry; older historical entries become discoverable after they are rewritten.
Use mise cache clear --task <task> to delete only that task's local output-cache entries and freshness pointer. Declared outputs in the working directory and entries belonging to other tasks are not removed. Legacy current entries without identity metadata are detached but retained because their ownership cannot be verified; mise warns when this occurs, and mise cache clear removes them.
Environment variables and cache keys
task_config.global_env adds ambient variable names to every enabled task cache in the config scope, including tasks with a task-local cache value. Unlike the default values under task_config.cache, these names always compose with task-local cache.env.
[task_config]
global_env = ["CI", "NODE_ENV"]For cache-enabled tasks, variables named in cache.env or task_config.global_env remain available when environment inheritance is denied. Disabled and non-cache tasks do not inherit variables through cache configuration. Use pass_through_env for variables that a task needs at runtime but that must not affect its cache key, such as short-lived credentials. The scoped task_config.global_pass_through_env equivalent applies to every task. In mise's default, non-sandboxed environment mode, ambient variables already pass through; these options matter when environment sandboxing is active through allow_env, deny_env, deny_all, or a corresponding CLI option.
[task_config]
global_pass_through_env = ["CI_JOB_TOKEN"]
[tasks.build]
pass_through_env = ["NPM_TOKEN"]Pass-through variables can change task behavior without invalidating cached results. Tasks should not use them for values that affect generated outputs. Their values are not added to the key or persisted as cache metadata, but a task can still expose them by writing them to cached output files or logs.
Storage, retention, and output replay
Cache entries are stored under MISE_CACHE_DIR/task-artifacts/v2 by default. Set the experimental task.cache_dir setting or MISE_TASK_CACHE_DIR to choose a different parent directory; mise keeps the artifact format in its v2 child directory. Default and custom locations are included in mise cache clear and manual and automatic cache pruning. Only successful task runs are cached. Cache read/write failures are treated as misses and never turn a successful task run into a failure.
New cache entries include a BLAKE3 artifact checksum that is independent of the cache lookup key. It covers the archived outputs and captured task result metadata, and mise verifies it before extracting files or replaying output. Entries written before checksums were introduced remain readable. mise cache task <task> --json includes the checksum for cache inspection tooling.
Readers, writers, inspection, and task-scoped deletion coordinate through a cross-process lock for each cache key. Concurrent processes therefore see a complete archive and manifest pair instead of mistaking an in-progress replacement for corruption; writers for unrelated keys remain independent. Temporary archive and manifest files are removed when a write fails normally. On a later cache use, mise also removes partial files abandoned by an interrupted process after acquiring the associated cache-key lock, so it never deletes files that an active writer is still publishing.
Set task.cache_max_size to bound the total artifact cache size, or task.cache_max_age to expire entries based on their last access. Both limits are optional and apply after successful cache writes. When a size limit is exceeded, mise removes least-recently-accessed entries first.
When a cache-enabled task executes instead of restoring a result, mise reports the reason: no matching entry, a corrupt entry, forced execution, disabled reads, or a dependency that completed without a stable cache key. Raw and dry-run cache bypasses retain their existing warning or preview behavior and are not reported as cache misses.
Stdout and stderr are stored as ordered, redacted streams and replayed using the output mode selected for the cache hit. Prefix, interleave, keep-order, timed, replacing, quiet, silent, and per-stream silence therefore apply to replayed output just as they do to live output. Raw and interactive tasks retain inherited terminal I/O and conservatively bypass artifact caching.
Cacheable dependencies contribute their artifact keys to dependent task keys, so a dependent can restore the matching artifact after its dependencies execute, skip, or restore. If a dependency executes without a stable artifact key, its dependents conservatively execute.