Skip to main content

Cache Explainability

Use --explain to understand why a graph task reran.

broski run build --explain

Every reason is derived by diffing the current manifest against the previous execution record, key by key. The exact strings Broski prints (verbatim, not paraphrased) are:

Reason stringTrigger
cache miss: no prior execution recordTask has never run successfully before.
cache miss: input changed: <path> / added: <path> / removed: <path>An @in-resolved file's content hash differs from the last run.
cache miss: input pattern changed: <pattern> / added / removedThe raw @in glob pattern list itself changed (not the matched files — the patterns).
cache miss: env changed: <key>A declared @env value changed.
cache miss: secret env changedA @secret_env value changed. No key name or value is ever printed — this reason is intentionally generic.
cache miss: output contract changed: <output> / added / removedThe task's declared @out paths changed.
cache miss: task command changedThe rendered shell command (after interpolation/param binding) changed.
cache miss: isolation mode changed@isolation (or the effective platform default) changed.
cache miss: task identity changedThe task's own name-derived manifest key changed (rare — usually only via --force-adjacent internal state).
cache miss: var:<name> changedA top-level variable or task parameter that feeds the fingerprint changed. There is no separate "task parameter changed" string — parameters and variables share the same var: manifest namespace.
cache bypass: --force supplied--force flag on the CLI.
cache bypass: --no-cache supplied--no-cache flag on the CLI.
cache bypass: force-rerun requestedThe TUI's x (single task) or X (whole target) force-rerun key, or programmatic RunOptions::force_tasks.
cache bypass: interactive modeInteractive tasks (@mode interactive) never read or write the cache at all.

If none of the tracked manifest keys differ but the overall fingerprint still doesn't match, Broski falls back to the generic cache miss: fingerprint changed — this should be rare in practice and usually points at an untracked source of nondeterminism (e.g. a dynamic top-level variable whose command output isn't stable).

Per-phase timing (--explain and BROSKI_PROFILE)​

When --explain is set — or the BROSKI_PROFILE environment variable is set to a truthy value, independent of --explain — Broski also prints a per-phase timing breakdown for each task:

timing: resolve_inputs=4ms fingerprint=12ms cache_restore=0ms stage_prep=8ms command_exec=1340ms output_promote=6ms cache_store=3ms

The phases match the boundaries emitted on the internal ProgressEvent::TaskPhase stream: ResolveInputs, Fingerprint, CacheRestore, StagePrep, CommandExec, OutputPromote, CacheStore. This is the fastest way to tell whether a slow task is slow because of the command itself or because of staging/fingerprinting overhead.

Explain workflow for migration debugging​

  1. Run target once.
  2. Run same target with --explain.
  3. Change one thing only, for example a single input file.
  4. Run again with --explain.
  5. Verify exactly one expected miss reason appears.
broski run build
broski run build --explain
echo "// change" >> src/main.rs
broski run build --explain

Secret handling​

If you use @secret_env, explain output remains actionable without exposing secret values.

Expected:

  • secret-derived changes are reported generically (cache miss: secret env changed, no key or value)
  • secret values ≥6 characters are redacted from captured stdout/stderr; shorter values are not pattern-matched
  • secret values are fingerprinted via a keyed BLAKE3 digest (salt at .broski/config/salt, mode 0600), never stored or diffed in plaintext