cache (build cache)
re-shell run is backed by a content-addressed build cache. When a task’s
inputs haven’t changed, its result is replayed from the cache instead of being
re-run: the declared outputs are restored to disk, the captured logs are
replayed, and the node is reported as cached with no process spawned.
The cache is on by default, fully offline and deterministic, and tamper-evident: every cached artifact is HMAC-signed and re-verified before it is ever trusted. A corrupt or tampered entry is treated as a miss, so a bad cache can never poison a build — the worst case is a normal run.
re-shell run build # 1st run: spawns + caches each taskre-shell run build # 2nd run: every unchanged task is `cached`re-shell run build --no-cache # bypass the cache entirely (always spawn)re-shell run build --cache-dir /tmp/rs-cachere-shell cache stats # size, entry count, hit-ratere-shell cache clean # prune the whole local cacheHow a cache key is computed
Section titled “How a cache key is computed”A cache key uniquely identifies the result of running one (package, task).
Two runs that would produce the same artifacts always produce the same key, and
any change that could alter the result produces a different key. The key is a
single SHA-256 over a canonical, sorted-key JSON of five inputs:
- Task command — the
package.jsonscript body for the task (not just its name). Changingtsctotsc --strictchanges the key. - Input file hashes — the SHA-256 of every input file, folded in as
relPath\0sha256so both a content change and a rename change the key. By default the input set is the whole package directory minus declaredoutputs,node_modules,.git,.re-shell, anddist. Declare explicitinputsglobs to narrow it. - Dependency closure keys — the cache keys of the task’s upstream dependency edges (sorted, so the order they were discovered in is irrelevant). An upstream change therefore cascades into every downstream key.
- Toolchain fingerprint —
process.version(Node), the detected package manager, and any per-language versions discovered offline from.nvmrc,.node-version,.python-version,.tool-versions, thegodirective ofgo.mod, andrust-toolchain[.toml]at both the package and the workspace root. Bumping any of these invalidates the cache. - Allow-listed environment subset — only
NODE_ENV,CI,BABEL_ENV,GO_ENV, andPYTHON_ENVcan influence a key. Any other environment variable is invisible to the cache, so a noisy local shell never busts it.
Nothing here spawns a process, reads the network, or reads a clock — the same tree on disk always yields the same key, which is what makes the cache safe.
Declaring inputs and outputs
Section titled “Declaring inputs and outputs”The cache learns what to hash and what to restore from the tasks config in
re-shell.workspaces.yaml. Both globs are optional and resolved relative to each
package directory:
tasks: build: dependsOn: ["^build"] inputs: ["src/**", "package.json", "tsconfig.json"] outputs: ["dist/**"]outputsare the artifacts captured on a miss and restored on a hit. They are also excluded from the default input set so a task’s own output never feeds back into its own key. Withoutoutputs, nothing is restored on a hit (the task is still skipped, but it must produce no files to be useful).inputsnarrow the hashed file set. Omit it to hash the whole package directory (minus the excluded dirs above). Narrowinginputsto the files a task actually reads gives more cache hits.
What gets cached
Section titled “What gets cached”Only successful runs (exit code 0) are cached — a failure is never
replayed as a hit. Each cached entry records:
- the exit code,
- the captured output artifacts (the bytes of every file matching
outputs), - the combined stdout/stderr logs, replayed verbatim on a hit, and
- the SHA-256 of every artifact, which binds the bytes to the entry.
On a hit the runner restores the outputs to disk (with a path-traversal guard that refuses to write outside the package directory) and replays the logs, without spawning the script.
Local vs remote (hub) backends
Section titled “Local vs remote (hub) backends”Both backends implement the same content-addressed contract — has, get,
put — so the runner treats them identically.
Local filesystem cache (default)
Section titled “Local filesystem cache (default)”Entries live under the workspace-local .re-shell/cache directory, sharded by
the first two hex characters of the key (<root>/<key[0:2]>/<key>/). Each entry
is written atomically: artifacts and the record are built in a temp directory,
then moved into place. Use --cache-dir (or --cache-dir on the cache
commands) to point at a different root, e.g. a shared mount.
Remote cache + CI hydration (off by default)
Section titled “Remote cache + CI hydration (off by default)”A remote cache (served by the hardened local hub) is off unless you opt in:
export RE_SHELL_REMOTE_CACHE="https://hub.internal/cache-api"export RE_SHELL_REMOTE_CACHE_TOKEN="…" # optional bearer tokenre-shell run buildWhen configured, lookups are remote-then-local: a CI runner with an empty local store hydrates from the remote first. A remote hit is seeded into the local store so subsequent runs hit instantly. On a miss, the freshly captured entry is pushed to the remote (best-effort — a push failure never fails the build). The remote uses the same HMAC verification as the local backend; a tampered envelope is rejected and surfaced as a miss.
HMAC signing and tamper rejection
Section titled “HMAC signing and tamper rejection”Every entry is signed with an HMAC-SHA-256 secret from RE_SHELL_CACHE_SECRET
(falling back to a stable per-machine default for purely local use). For a
shared or remote cache you must set an explicit secret on every machine
that reads or writes it.
On put, three things are signed: the record, the canonical digest of the
artifact set, and the SHA-256 of each individual artifact. On get, all three
are re-verified — the record signature, the files-digest signature, and a
re-hash of every artifact against its bound SHA-256. Signature comparison is
constant-time. Any mismatch — a flipped byte in an artifact, an edited
record, an added/removed file, or the wrong secret — rejects the whole entry,
which the runner treats as a miss and falls back to a real run.
--no-cache
Section titled “--no-cache”re-shell run <task> --no-cache disables the cache for that invocation: every
task is spawned, nothing is read from or written to the cache, and no task is
ever reported as cached. Use it to force a clean run or to measure cold-build
time.
cache stats
Section titled “cache stats”Read-only, offline summary of the local cache root:
re-shell cache statsre-shell cache stats --jsonre-shell cache stats --cache-dir /tmp/rs-cache▶ build cache stats
location /repo/.re-shell/cache entries 42 size 18.3 MiB hit-rate 74.2% (115 hits / 40 misses)The hit-rate is cumulative across runs (tracked in a small telemetry file
alongside the entries) and reads n/a until at least one run has been recorded.
--json emits a single-line typed envelope conforming to the
JSON contract:
{ "ok": true, "data": { "location": "/repo/.re-shell/cache", "entries": 42, "sizeBytes": 19184384, "hits": 115, "misses": 40, "hitRate": 0.742 }}hitRate is null when no runs have been recorded.
cache clean
Section titled “cache clean”Prune the entire local cache and reset its telemetry:
re-shell cache cleanre-shell cache clean --jsonre-shell cache clean --cache-dir /tmp/rs-cache{ "ok": true, "data": { "location": "/repo/.re-shell/cache", "removedEntries": 42, "reclaimedBytes": 19184384 }}Cleaning a non-existent cache root is a no-op (zeros). On error, both commands
emit a CACHE_ERROR envelope in --json mode and exit non-zero.
Environment variables
Section titled “Environment variables”| Variable | Purpose |
|---|---|
RE_SHELL_CACHE_SECRET | HMAC secret for signing/verifying artifacts. Required for any shared or remote cache. |
RE_SHELL_REMOTE_CACHE | Base URL of the remote cache. Setting it (non-empty) enables the remote backend. |
RE_SHELL_REMOTE_CACHE_TOKEN | Optional bearer token sent as Authorization: Bearer <token> to the remote. |
NODE_ENV, CI, BABEL_ENV, GO_ENV, PYTHON_ENV | The only environment variables that participate in a cache key. |