Caching Scripted Workflows

The first scripted workflow declared an input, an output, and a working directory. Add more headers only when another fact can change the result.

Describe Every Relevant Input#

Track files and directories with input. Track selected environment variables with env, and use fingerprint for tool or host facts that are not files:

bash
#!/usr/bin/env -S once exec -- bash # once input "../src/**/*.ts" # once input "../package-lock.json" # once output "../dist/" # once env "NODE_ENV" # once fingerprint "node --version" # once cwd ".." npm run build

Here the source files, lockfile, NODE_ENV value, Node.js version, and script file all contribute to the cache key. The script uses the npm package manager to run the build. Once forwards only declared environment variables. A fingerprint command runs before the action; if it fails, Once stops before running the script.

Keep The Contract Precise

Declare facts that can change the result, not every file or host value the script can see. A precise contract produces useful cache hits while still invalidating the result when it should.

Continue During a Cache Outage#

If a remote cache remains unavailable after bounded retries, Once executes the script instead of reusing a cached result. Newly produced results remain in the local cache, so later runs can reuse them even while the remote cache is down. The outage is logged; it is not treated as proof that remote content is absent.

Builds can also reconstruct an unavailable deferred output when the current build recorded its producer. Reconstruction must reproduce the exact expected bytes and remains subject to resource admission and bounded repair limits. An output without a recorded producer still fails if its bytes cannot be fetched.

Cache reuse and restoration still fail on rejected credentials, corrupt content, malformed cache records, and local filesystem errors. Explicit cache reads, including SDK reads, continue to report availability errors rather than silently return missing data.

Connect Dependent Scripts#

Use needs when one annotated script must run before another:

bash
#!/usr/bin/env -S once exec -- bash # once needs "./generate-assets.sh" # once input "../src/**/*.ts" # once output "../dist/" # once cwd ".." npm run build

Once runs independent dependencies concurrently. A dependency's declared outputs become declared inputs of the dependent script, and their fingerprints become part of its cache key. This lets a remote test consume an installation script's node_modules output without exposing the rest of the host workspace. If the dependency runs again but produces the same outputs, the dependent script can still reuse its previous result.

Check Repeated Execution#

A second cache hit checks cache reuse, not whether executing the command again produces the same result. To compare fresh executions of the greeting script from Scripted Workflows, run:

bash
once exec --verify-reproducible --script -- bash scripts/greet.sh

Once snapshots the whole workspace, then runs two trials from that same snapshot at the same private workspace path. Each trial has its own initially empty local Once cache. Script dependencies declared with needs and commands declared with fingerprint run inside each trial, rather than reusing work performed before the check. Declared outputs stay in the private trials; Once does not replace outputs in your original workspace with trial artifacts.

The snapshot excludes .once runtime state and known declared outputs of the script and its dependencies. Outputs that overlap that script's declared inputs stay in the snapshot, so in-place updates start from the same input in each trial. Other pre-existing generated files remain part of the starting snapshot. This is not a clean checkout or a way to discover every undeclared input. Symlinks pointing outside the workspace and special files prevent the snapshot from being made; materialize required files inside the workspace or remove those entries before retrying. A script cannot declare the whole workspace as an output for this check; declare its generated paths instead.

Read The Result#

Both trials must execute successfully. Two identical failures never pass. Once reports artifact equality separately from captured standard output and standard error:

  • The comparison covers the selected script's declared result, not artifacts and logs produced only by prerequisites. Prerequisites still run in each trial.
  • When outputs are declared, artifact differences fail the check. Log-only differences are advisory and do not fail a matching-artifact check.
  • Without declared artifacts, the check uses captured logs instead. Matching logs do not establish that undeclared files match.

The command exits successfully only when those conditions hold. A passing check means no artifact divergence observed in these two trials, or matching captured logs when no artifacts are declared. It is not proof of reproducibility.

Inspect Retained Trials#

The result prints a verification UUID and a command to inspect it again:

bash
once query verification <ID> once query verification <ID> --format json

These queries read the retained report without executing the script again. The report is stored at .once/verifications/<ID>/report.json. Each trial-1 and trial-2 directory retains result.json, decoded declared artifacts under outputs/, captured stdout and stderr when present, and its local Once cache. Failed trials also retain their workspace for debugging. File-level artifact differences include bounded text excerpts and the first differing byte offset when available. If the detail listing is truncated, inspect the full retained artifacts instead. The verification report reference describes the JSON fields used by command-line tools and coding harnesses.

Reports, artifacts, and logs are local runtime state, not automatically published evidence. They can contain sensitive source or output; inspect them before sharing and remove .once/verifications/<ID>/ when no longer needed.

Know The Boundary#

Only run a repeated-trial check when you authorize repeated execution of the script and all its dependencies. Host toolchains, host tool caches, absolute paths, network services, and external side effects are not isolated. Commands using those resources can still change host or external state. Once does not perturb or control randomness, clocks, or thread scheduling, and both trials use the same private path, so path-sensitive differences can remain hidden. Script verification currently requires local execution, including every needs dependency; scripts selecting remote execution are rejected.

To deny Internet sockets, including loopback, add --network deny to once exec. This policy also applies to needs dependencies and refuses execution when enforcement is unavailable. Local Unix-domain communication remains available. Fingerprint commands cannot currently enforce this policy, so a script with a fingerprint fails closed under --network deny, rather than executing the probe online. Replace the fingerprint with declared inputs before running offline.

Directory outputs use materialize-external by default. When an output contains a symbolic link whose target is outside that output directory, Once copies the target into the cached result. This makes restored directories such as node_modules/ independent of a package manager's original storage location.

Use output-symlinks only when the script needs a different policy:

bash
# once output "../dist/" # once output-symlinks "preserve"

preserve stores links as links. Use it only when the link targets will exist at restore time.

Supported Headers#

HeaderPurpose
inputTracks files, directories, or globs.
needsRuns another annotated script first and includes its output fingerprints in this script's cache key.
fingerprintRuns a read-only command and includes its text, exit status, standard output, and standard error in the cache key.
outputDeclares files or directories to restore on cache hits.
output-symlinksUses materialize-external or preserve when capturing symbolic links in outputs.
envForwards a selected environment variable and includes its value in the cache key.
cwdChooses the working directory for the script body.
remoteSelects a named remote execution infrastructure provider.

The script file itself is always part of the cache key, even when it has no input headers.

Use Another Script Runtime#

The same headers work in languages that use line comments. Put the interpreter after once exec -- in the shebang:

python
#!/usr/bin/env -S once exec -- python3 # once input "../src/**/*.py" # once output "../dist/" print("build")
ruby
#!/usr/bin/env -S once exec -- ruby # once input "../lib/**/*.rb" # once output "../dist/" puts "build"
elixir
#!/usr/bin/env -S once exec -- elixir # once input "../lib/**/*.ex" # once output "../dist/" IO.puts("build")

Once also recognizes //, ;, --, %, and ' line-comment forms. To keep an existing shebang, invoke the interpreter explicitly:

bash
once exec -- python3 scripts/build.py

Once detects the headers and applies script-aware execution. Pass --script when you need to force that mode.

Use A Mise File Task#

Once can wrap a Mise file task without changing how callers invoke it:

bash
#!/usr/bin/env -S once exec -- bash #MISE description="Build assets" # once input "../src/**/*.ts" # once output "../dist/" npm run build

After saving the task in a Mise task directory, run it normally:

bash
mise run build

Next#

Configure Infrastructure when these cacheable results should be shared across machines, or use Remote Execution to run them in a fresh sandbox. If an existing tool must probe or populate the cache itself, continue to Manual Cache Access.