Runtime coverage
See which functions your production traffic runs, which never run, and which hot paths a change touches. Runtime coverage merges execution data into fallow health, so you can delete cold code with evidence.
Runtime coverage tells you which functions ran when your deployed app handled real traffic. Use it to delete cold code with confidence and to spot changes to hot code in review.
Static analysis finds exports with no references and files that no test reaches. Runtime coverage adds the production view on top of fallow health.
fallow license activate --trial --email you@company.com
fallow coverage setup
fallow health --runtime-coverage ./coverage
What it tells you
When you enable runtime coverage, fallow merges runtime evidence into the health report. Each function gets a per-finding verdict. The verdict combines the static reachability signal with the invocations that fallow observed in production:
| Verdict | Meaning |
|---|---|
safe_to_delete | Statically unused AND V8 tracked AND zero invocations. This is the strongest delete signal from fallow. |
review_required | Statically used but never invoked in production. A person must check it. The function can be seasonal, used only on an error path, or really dead. |
low_traffic | Invoked in production, but below the --low-traffic-threshold ratio of the total trace count (spec default 0.1%). Effectively dead for the observed period. |
coverage_unavailable | V8 could not track the function (lazy-parsed, worker-thread isolate, dynamic eval, or the source map did not resolve). Information only, not a dead-code signal. |
active | Invoked in production above the low-traffic threshold. Not dead. |
unknown | A forward-compatibility value that newer sidecars can return. Rare. |
The report also has these fields:
| Output | Meaning |
|---|---|
verdict | Overall report status: clean, cold-code-detected, hot-path-touched, license-expired-grace, or unknown. In PR review, when --diff-file (or --changed-since) is set, hot-path-touched takes priority over cold-code-detected. |
signals | Every signal that the post-processing pass detected, as an array. verdict shows only the most actionable signal for the current context, and signals shows all of them. Sorted by severity, highest first. Omitted when empty. A typical PR run emits ["cold-code-detected", "hot-path-touched"] when both apply. |
summary | Aggregate counts and capture metadata. See Summary fields. |
findings | Cold or unresolved functions. See Finding fields. |
hot_paths | The busiest runtime functions, with a stable id, an optional stable_id join key, line, end_line, invocations, percentile rank, and an optimization_target block. See Optimization target fields. |
blast_radius | For each runtime-observed function, how far a change can spread: caller_count, caller_count_weighted_by_traffic, deploys_touched, and a risk_band. |
importance | For each runtime-observed function, how much it matters in production: invocations, cyclomatic, owner_count, an importance_score, and a reason. |
actionable | Whether the report has enough evidence to act on, with actionability_reason and actionability_verdict. A capture with no tracked functions reports an insufficient_evidence verdict, so fallow does not read it as cold. This verdict can only hold back action: it never gates a positive verdict or a confidence score. |
provenance | Context about the data behind the report (data_source, is_production, freshness_days, untracked_ratio, unresolved_ratio, stale, stale_after_days). Context only. It never gates a verdict. |
watermark | Set when a license or trial is in its grace period, for example license-expired-grace when the license expired 8 to 30 days ago. The analysis still runs. See Trial and watermark behavior. |
warnings | Non-fatal merge diagnostics, for example partial source-map remapping. |
Summary fields
- Aggregate counts:
functions_tracked,functions_hit,functions_unhit,functions_untracked,coverage_percent. trace_count,period_days, anddeployments_seen.- An optional
capture_qualityblock (window_seconds,instances_observed,lazy_parse_warning,untracked_ratio_percent). Sidecars on protocol 0.3 or newer emit it. It flags short-window captures in which lazy-parsed scripts may not appear.
Finding fields
id: a per-finding suppression id (fallow:prod:<hash>). The hash covers the current line.stable_id(optional): a cross-surface id (fallow:fn:<hash>). The hash covers file + name + start line. One function has one value across findings, hot paths, blast radius, and importance, and across the V8, Istanbul, and oxc producers.source_hash(optional): a content digest that does not change when a line moves. A function that moved but was not edited keeps the same value, so its baseline stays suppressed after a pure line shift.line, the per-findingverdict,confidence, supportingevidence, and machine-readableactions.discriminators(optional): the inputs behind the verdict, so an agent can reproduce the verdict without deriving it again. The block has:tracking_state:called,never_called, oruntracked.invocation_ratio.- The
low_traffic_thresholdandmin_observation_volumein effect. trace_countwithmeets_observation_volume.
Every finding has an evidence block. It gives the reason for the verdict in machine-readable form:
"evidence": {
"static_status": "used",
"test_coverage": "not_covered",
"test_only_reference": false,
"v8_tracking": "tracked",
"untracked_reason": null,
"observation_days": 30,
"deployments_observed": 14
}
test_only_reference covers the one case where production mode and static reachability disagree. Production mode drops test, spec, story, fixture, and benchmark files from discovery. An export that only those files call therefore reads as statically unused.
true: the function is unreachable in the production graph, but an excluded file still references it. It is not dead code, because a removal breaks the test that references it. Fallow reports it asreview_required, notsafe_to_delete, with the action "Only tests reference this export; delete the test usage together with the function or keep it".false: fallow compared the production graph with the full tree and found no such reference.null: the report ran without a production filter, so there is no second reachability answer.
Optimization target fields
A hot path tells you how often a function runs. The optimization_target block also tells you how much work each call does, so you know where speed work gives the largest gain. importance ranks the risk of a change. optimization_target ranks the speed gain.
| Field | Meaning |
|---|---|
cost_score | The invocations multiplied by the work per call. An integer with no upper limit. Compare it only between hot paths with the same cost_basis. |
cost_basis | The input for the work per call: inner_iterations or cognitive. |
inner_iterations_per_call | The peak runs of one block inside the function for each call. 1.0 means no block ran more than once per call. A loop body that runs 3 times per call gives 3.0. Calls to other functions do not change the value: a function that calls a helper 3 times per call still gives 1.0 when it has no loop. Present only when the dump has V8 block counts for the function. |
cognitive | The static cognitive complexity of the function. |
cyclomatic | The static cyclomatic complexity of the function. |
line_count | The number of lines in the function body. |
The block counts come from the V8 dump. Node writes them under NODE_V8_COVERAGE, so a dump from a test run or a benchmark run gives the measured value. When the dump has block counts for the function, cost_basis is inner_iterations. When it has none (an Istanbul file, or a cloud pull), cost_basis is cognitive. Cognitive complexity is a static proxy for the work per call, not a measurement. On the cognitive basis, the work per call is at least 1.
One report can hold both bases. A dump with block counts also gives cognitive for a function that shares its start line with another function, or that fallow cannot map to a source line. Scores on the two bases do not compare, so sort by cost_basis first.
A hot path has no optimization_target block when it has no stable_id, or when no function in the checkout matches the stable_id. The optimization_target_unmatched warning gives the count of these hot paths in the output.
The runtime coverage pass selects hot paths by invocations, and --top keeps the busiest. A function with fewer invocations but a large amount of work per call can be absent from the list. Raise --top, or lower --min-invocations-hot, to see more candidates.
To list the best speed targets from the JSON output:
fallow health --runtime-coverage ./coverage --format json --quiet \
| jq '.runtime_coverage.hot_paths
| map(select(.optimization_target))
| sort_by(.optimization_target.cost_basis != "inner_iterations",
-.optimization_target.cost_score)
| .[:10]
| map({path, line, function, invocations, basis: .optimization_target.cost_basis,
cost: .optimization_target.cost_score,
per_call: .optimization_target.inner_iterations_per_call})'
The human output shows the cost at the end of each hot path line, for example (600 invocations, p100, cost 1,800 at 3.00 iterations/call).
Confidence thresholds
Two flags control how strict fallow is when it assigns verdicts:
| Flag | Default | Effect |
|---|---|---|
--min-observation-volume <N> | 5000 | Minimum total trace volume before the sidecar can emit high-confidence safe_to_delete or review_required verdicts. Below this value, confidence is capped at medium, so new or low-traffic services do not get overconfident verdicts. |
--low-traffic-threshold <RATIO> | 0.001 | An invoked function below this fraction of trace_count is classified low_traffic instead of active. A decimal value (0.001 = 0.1%). |
Static coverage vs runtime coverage
fallow health has three coverage options. Each answers a different question:
| Surface | Flag | Input | Answers | Paid |
|---|---|---|---|---|
| Static reachability gaps | --coverage-gaps | none | Which runtime files and exports have no transitive test path? | no |
| Exact CRAP scoring | --coverage | Istanbul JSON | How covered is each function for CRAP calculation? | no |
| Runtime coverage | --runtime-coverage | V8 directory, V8 JSON file, or Istanbul JSON | Which functions actually ran, which stayed cold, and which hot paths changed? | A single local capture is free. More needs a license. |
To find untested code, use --coverage-gaps. For more accurate CRAP scores, use --coverage. For evidence about the code that production runs, use --runtime-coverage.
Accepted input
fallow health --runtime-coverage <path> accepts:
| Input | Example |
|---|---|
| V8 dump directory | ./coverage created with NODE_V8_COVERAGE=./coverage |
| Single V8 JSON file | ./coverage/12345.json |
| Single Istanbul coverage map JSON file | ./coverage/coverage-final.json |
If a V8 dump includes Node's source-map-cache, fallow remaps the supported source-map URLs before analysis. Supported URLs include file paths, relative paths, webpack://..., and vite://.... For an unsupported virtual scheme, fallow uses raw V8 handling and the run continues.
A remapped function count has a ceiling of 4,294,967,295 calls. The remap writes 32-bit counts, so a function that V8 counted more often than that reports the ceiling as its invocations. Only very hot helpers in long-running processes reach it.
Production coverage: getting source attribution
Production runtime coverage records positions in your built bundle. Examples are a Next.js .next/server/chunks/*.js, an Electron out/main/index.js, a NestJS dist/, or a minified browser bundle. Bundle positions cannot map back to your TypeScript or JSX source on their own.
If you do not take one of the two steps below, the cloud records every function as unresolved. fallow coverage analyze --cloud then shows no attributable findings, and a coverage_unresolved warning explains why.
Pick one:
-
Upload source maps (for V8 coverage). After the build, upload the
.mapfiles in a CI step. The cloud uses them to map bundle positions back to source:fallow coverage upload-source-maps --dir <build-dir> # e.g. dist, .next, outUpload the maps for the same commit on which you collected the coverage.
fallow coveragedocuments the flags, per-commit SHA matching, and CI snippets. Whenfallow coverage analyze --cloudfinds built.mapfiles on disk that you did not upload, it prints the exact command to run. -
Instrument at build time with Istanbul (no maps needed).
oxc-coverage-instrumentrewrites your source and puts the original file and line positions in the coverage itself, so nothing needs remapping. The fallow project uses this path for its own code.
First-run workflow
- Optional: start a trial or activate a license with
fallow license. A single local capture works without one. - Run
fallow coverage setup. - Follow the generated recipe in
docs/collect-coverage.md. - When coverage exists, run
fallow coverage setuporfallow health --runtime-coverage <path>again.
fallow coverage setup is resumable. It does these steps:
- Checks the license state.
- Installs or locates
fallow-cov. - Writes a collection recipe for your framework.
- Detects an existing coverage artifact, when one is present.
- Continues into
fallow health --runtime-coverage.
Review changed code
In change review, runtime coverage shows which hot code a change touches:
fallow health --changed-since main --runtime-coverage ./coverage
When changed files touch hot runtime code, fallow can upgrade the overall verdict to hot-path-touched. PR review then has a stronger signal than static complexity alone.
For tighter line-level scoping, pass a pre-computed unified diff:
git diff --unified=0 main...HEAD > /tmp/pr.diff
fallow health --runtime-coverage ./coverage --diff-file /tmp/pr.diff
Fallow then flags a hot function only when the diff changes a line inside its [start_line, end_line] range, not anywhere in the file. The bundled GitHub Action and GitLab CI template already pre-compute the diff and pass it in. PR comments therefore show only the hot paths that the change touches.
Line-level hot-path matching needs protocol 0.5 or newer in the sidecar. Older sidecars report only a start line, so fallow uses file-level scoping.
Trial and watermark behavior
Runtime coverage is part of Fallow Cloud, the paid product. A single local capture is free and needs no license. Continuous or multi-capture monitoring (for example, a V8 directory with more than one JSON file) needs a license with the runtime_coverage feature. To try it, start a 30-day trial with fallow license activate --trial --email <addr>.
Fallow verifies the license offline against a compiled-in Ed25519 public key. Only fallow license activate --trial and fallow license refresh need network access.
When a license expires, fallow gives you a grace period:
| Days after expiry | Behavior |
|---|---|
| 1 to 7 | The analysis runs as normal. fallow license status tells you to refresh. |
| 8 to 30 | The analysis runs. The output gets the license-expired-grace watermark and verdict. |
| 31 or more | Fallow blocks runtime coverage and exits with code 3. Run fallow license refresh. |
When a license is present, fallow checks it for every runtime coverage run, also for a single capture. An expired license past the grace period, an invalid token, or a license without the runtime_coverage feature stops the run with exit code 3. For details, see fallow license.