fallow coverage
Use production runtime data to see which code runs and which code you can delete. Fallow coverage runs the resumable setup flow, analyzes local or cloud runtime coverage, uploads source maps, and uploads the static inventory that shows untracked code in the dashboard.
Static analysis tells you what code can run. Runtime coverage tells you what code did run in production, so you can delete cold code with more confidence and review hot paths with more care. fallow coverage groups the commands for runtime coverage, which is part of Fallow Cloud, the paid product. A single local capture is free. Continuous or multi-capture monitoring needs a license. The cloud commands need a Fallow Cloud API key:
fallow coverage setup # resumable first-run flow
fallow coverage analyze --cloud --repo owner/repo
fallow coverage upload-inventory # push a static function inventory
fallow coverage upload-source-maps # upload build source maps for bundled code
setup
You can stop and resume the setup flow. Run it once to start, follow the generated recipe, and then run it again after you capture traffic. It does four steps:
- check license state
- locate or install
fallow-cov - write a framework-specific collection recipe to
docs/collect-coverage.md - if coverage already exists, run
fallow health --runtime-coverage <path>directly
fallow coverage setup
fallow coverage setup --non-interactive
fallow coverage setup --yes
fallow coverage setup --yes --json
fallow coverage setup --yes --json --explain
Flags
| Flag | Description |
|---|---|
-y, --yes | Accept prompts automatically. Use it for local setup when you want fallow to continue without confirmation prompts. |
--non-interactive | Print instructions and do not prompt. Use it in CI, remote shells, or agent workflows. |
--json | Print deterministic setup instructions as JSON that an agent can read. Implies --non-interactive. It does not prompt, write files, install packages, activate a license, or make network calls. See Agent-readable JSON. |
--explain | With --json, include a _meta block with field definitions, enum values, warning semantics, and this docs URL. |
What setup detects automatically
fallow coverage setup reads package.json, lockfiles, and scripts, and writes instructions for your project:
| Detection | Purpose |
|---|---|
| Framework | Chooses a recipe for Next.js, NestJS, Nuxt, SvelteKit, Astro, Remix, Vite browser apps, plain Node services, or a generic fallback. When one package.json has both a Node-server framework (Elysia, Hono, Fastify, Express, Koa, @trpc/server) and Vite, the Node-server framework wins. The recipe then targets the API and not the bundled client. |
| Workspace topology | Reads workspaces (npm, pnpm, yarn, bun) and writes one recipe for each member that runs code. Skips library-only workspaces (no start, preview, or dev script and no Node-server dependency). The top-level runtime_targets array is the union of all members (["node"], ["browser"], or ["node", "browser"]). |
| Package manager | Uses packageManager, lockfiles, or both to choose install commands for npm, pnpm, yarn, or bun. |
| Coverage artifact | Detects existing coverage/coverage-final.json, .nyc_output/coverage-final.json, or JSON-containing V8 directories. |
| Sidecar binary | Resolves FALLOW_COV_BIN, FALLOW_COV_BINARY_PATH, project-local shims, package-manager bin lookups, ~/.fallow/bin/fallow-cov, and PATH. |
If the project matches no built-in framework recipe, fallow still writes a fallback docs/collect-coverage.md. It links to the public coverage docs, so you can do the setup by hand.
Agent-readable JSON
Agents start with fallow coverage setup --yes --json. The payload is deterministic and does not change between runs. --explain adds the optional _meta keys and does not change schema_version.
This example is a workspace monorepo with an Elysia API at the root and two Vite browser apps in dashboard/ and site/:
{
"schema_version": "1",
"framework_detected": "plain_node",
"package_manager": "bun",
"runtime_targets": ["node", "browser"],
"members": [
{
"name": "fallow-cloud",
"path": ".",
"framework_detected": "plain_node",
"runtime_targets": ["node"],
"files_to_edit": [{"path": "src/index.ts", "reason": "..."}],
"snippets": [{"label": "Node entrypoint", "path": "src/index.ts", "content": "..."}],
"dockerfile_snippet": "ENV FALLOW_TRANSPORT=fs\nENV FALLOW_WRITE_TO_DIR=/tmp/fallow-coverage",
"warnings": []
},
{
"name": "fallow-dashboard",
"path": "dashboard",
"framework_detected": "vite",
"runtime_targets": ["browser"],
"files_to_edit": [{"path": "dashboard/src/main.ts", "reason": "..."}],
"snippets": [{"label": "Vite browser entry", "path": "dashboard/src/main.ts", "content": "..."}],
"dockerfile_snippet": null,
"warnings": []
}
],
"config_written": null,
"commands": ["bun add @fallow-cli/beacon", "bun add -d @fallow-cli/fallow-cov"],
"files_to_edit": [{"path": "src/index.ts", "reason": "..."}],
"snippets": [{"label": "Node entrypoint", "path": "src/index.ts", "content": "..."}],
"dockerfile_snippet": "ENV FALLOW_TRANSPORT=fs\nENV FALLOW_WRITE_TO_DIR=/tmp/fallow-coverage",
"next_steps": [...],
"warnings": []
}
framework_detecteduses canonical ids (nextjs,nestjs,nuxt,sveltekit,astro,remix,vite,plain_node,unknown).- In a workspace project,
members[]has one entry for each workspace that runs code. Each entry has its ownframework_detected,runtime_targets,files_to_edit,snippets, anddockerfile_snippet. - The top-level fields copy the first runtime member. This is the root when the root is a runtime app, and otherwise the first runtime workspace. In a monorepo with a browser app at the root and a Node child, agents must read the
dockerfile_snippetof each member, not the top-level one. - A single-app project has a
membersarray of length 1 (path"."), so scripts can handlemembers[]the same way for all projects.
Some repos only aggregate other packages. Their only runtime sign is a Turbo- or Nx-style dev script that calls the children, plus build-only library packages. Fallow does not treat these repos as runtime code and returns an empty payload: framework_detected: "unknown", runtime_targets: [], members: [], and a warnings entry of "No runtime workspace members were detected; emitted install commands only.". To detect this case, agents can check members.length === 0 (or framework_detected === "unknown") and skip the step that applies snippets.
Generated recipe
A generated docs/collect-coverage.md recipe usually looks like this:
1. Remove any old dump directory: rm -rf ./coverage
2. Build the app
3. Start the app with NODE_V8_COVERAGE=./coverage ...
4. Exercise the routes or jobs you care about
5. Stop the app and run: fallow coverage setup
Fallow uses your existing build, start, or preview scripts when they exist. Otherwise, it uses the common commands of your framework.
Typical flow
fallow license activate --trial --email you@company.com
fallow coverage setup
# Follow docs/collect-coverage.md, then run setup again.
# With coverage present it hands off to `fallow health --runtime-coverage ./coverage`.
fallow coverage setup
analyze
Use local mode when you have a coverage artifact on disk:
fallow coverage analyze --runtime-coverage ./coverage --format json
Use cloud mode to get the latest fallow.cloud runtime context for a repo:
FALLOW_API_KEY=fallow_live_... \
fallow coverage analyze --cloud --repo owner/repo --format json
FALLOW_API_KEY alone never selects cloud mode. You must pass --cloud or --runtime-coverage-cloud, or set FALLOW_RUNTIME_COVERAGE_SOURCE=cloud.
Analyze flags
| Flag | Description |
|---|---|
--runtime-coverage <PATH> | Local V8 directory, V8 JSON file, or Istanbul coverage map. Cannot be combined with cloud mode. A single local capture is free. Continuous or multi-capture monitoring needs a license. See fallow license. |
--cloud, --runtime-coverage-cloud | Explicitly fetch cloud runtime data from /v1/coverage/:repo/runtime-context. |
--repo <OWNER/REPO> | Repository to fetch in cloud mode. Falls back to $FALLOW_REPO, then to the parsed origin. Fallow URL-encodes the slashes, so the value is one route segment. |
--api-key <KEY> | Fallow Cloud bearer token. Falls back to $FALLOW_API_KEY, but only after you opt in to cloud mode. |
--api-endpoint <URL> | Override the API base URL. Use it for staging and on-prem deployments. |
--coverage-period <DAYS> | Cloud observation window, 1 through 90 days. Default: 30. |
--project-id <ID> | Optional ID that selects a monorepo or project. |
--environment <NAME> | Optional cloud environment filter. |
--commit-sha <SHA> | Optional cloud commit filter. |
--top <N> | Show only the top N runtime findings, hot paths, blast-radius entries, and importance entries. Fallow cuts the lists before it renders them, so JSON, human, and cloud-merge output get the same cut. |
--blast-radius | Show the blast-radius section in human output. When runtime coverage analysis runs, JSON always includes runtime_coverage.blast_radius. |
--importance | Show the importance section in human output. When runtime coverage analysis runs, JSON always includes runtime_coverage.importance. |
--production | Run analyze in production mode, the same as fallow health --production. Removes test files and dev-only code paths before fallow merges the runtime data. |
--min-invocations-hot <N> | Threshold for hot paths. A function that runs at least N times in the captured window is hot. Default: 100. Works like the same flag on fallow health --runtime-coverage. |
--min-observation-volume <N> | Minimum total trace volume for high-confidence safe_to_delete and review_required verdicts. Below this volume, the sidecar gives at most medium confidence. Default: 5000. |
--low-traffic-threshold <RATIO> | A function that runs, but below this fraction of the total trace count, is low_traffic and not active. Decimal form (0.001 = 0.1%). Default: 0.001. |
--debug-unmatched | On stderr, list each cloud runtime function that has no local match, highest traffic first. Use it to see what the join dropped, without a debugger. |
--explain | With --format json, add a top-level _meta block with field definitions, enum values (data_source, test_coverage, v8_tracking, action_type, and others), warning-code documentation, and the docs URL. |
Cloud analysis gives the same runtime_coverage JSON block as local mode. Its summary includes data_source: "cloud", last_received_at, and a capture_quality computed from the fetched runtime window. When fallow cannot match a cloud function to the local AST or static index, it leaves the function out of the findings and reports it in a cloud_functions_unmatched warning.
The join works even when the two sides name a function differently. It uses these steps:
- Fallow first matches on stable ids.
- For a runtime path such as
/app/src/a.tsfrom a containerized service, fallow maps the path onto the local tree. It compares the file name and then the path segments from the end. - Some functions have a runtime name that differs from the static index, for example an anonymous callback reported as
map,then, orget, or an accessor that keeps itsgetprefix. Fallow matches these on their position in the resolved file.
Steps 2 and 3 refuse to guess when the answer is ambiguous. In these cases, the function stays unmatched:
- two local files match one runtime path equally well
- two definitions start on the same line, with no end line between them
To list the functions that stay unmatched, pass --debug-unmatched.
The actions[].type of each finding uses canonical kebab-case values: delete-cold-code for verdict=safe_to_delete, and review-runtime for verdict=review_required. The sidecar can add other protocol-specific values. Treat an unknown value as a future extension, not as a schema violation.
Sidecar installation
If fallow-cov is missing, setup tells you what it checked and gives the install command for your package manager, for example:
pnpm add -D @fallow-cli/fallow-cov
To skip auto-discovery, set one of the two override env vars to a binary path. Fallow checks FALLOW_COV_BIN first, then FALLOW_COV_BINARY_PATH, then project-local auto-discovery. If the configured path does not exist, both env vars fail with a clear error. They never go to the next step silently. Use FALLOW_COV_BINARY_PATH for air-gapped enterprise installs, Linux distro-packaged sidecars, and Docker multi-user setups where ~/.fallow/bin is not writable.
Sidecar signature verification
Each time fallow starts the sidecar, it checks the Ed25519 signature against the public key built into fallow. The sidecar binary must have a <binary>.sig file next to it. A missing, wrong-length, or invalid signature fails with exit code 4, and fallow does not run the binary. There is no mode that warns and runs, and no env var to turn the check off. If the check fails, reinstall the signed distribution with the install command above.
upload-inventory
fallow coverage upload-inventory lets the dashboard show the functions that never ran in production. It reads each JS and TS source file in the project and POSTs a static function inventory to Fallow Cloud, with one row per declaration, expression, arrow, or method. For the matching git SHA, the server computes inventory − runtime-seen = untracked. The result fills the Untracked filter in the dashboard.
fallow coverage upload-inventory \
--api-key $FALLOW_API_KEY \
--project-id acme/web
Defaults and inference
| Flag | Default |
|---|---|
--api-key | $FALLOW_API_KEY |
--api-endpoint | $FALLOW_API_URL or https://api.fallow.cloud |
--project-id | $GITHUB_REPOSITORY, then $CI_PROJECT_PATH, then git remote get-url origin parsed to owner/repo |
--git-sha | git rev-parse HEAD |
Flags
| Flag | Description |
|---|---|
--api-key <KEY> | Fallow Cloud bearer token. Use $FALLOW_API_KEY on shared CI runners. Other processes can see a secret on the command line with ps, and it can leak into shell history or process audit logs. |
--api-endpoint <URL> | Override the base URL. Use it for staging and on-prem deployments. |
--project-id <ID> | Project identifier, for example owner/repo or a bare name such as my-app. |
--git-sha <SHA> | The commit SHA for this inventory. Max 64 chars, [A-Za-z0-9._-] only. |
--allow-dirty | Upload even when the working tree has uncommitted changes. Without this flag, fallow refuses the upload with exit code 10. With it, fallow prints a warning, because the inventory comes from the working copy and can differ from the git SHA. |
--exclude-paths <GLOB> | More globs to skip. Fallow applies them after the configured fallow ignore rules. Repeatable. |
--path-prefix <PREFIX> | Prefix that fallow adds in front of each filePath, so the static inventory paths have the same shape as the paths from the runtime beacon. Required for containerized deployments. The beacon reports the absolute V8 path inside the container (for example, /app/src/foo.ts). By default, the inventory has repo-relative paths (src/foo.ts). Must start with / and use POSIX separators. See Path prefix for common values. |
--with-callers | Also upload importer edges (the modules that import each declaration) with the inventory. The dashboard then shows the callers of untracked code. The inventory rows also have per-function complexity and per-file churn. |
--dry-run | Print what fallow would upload, and exit. No network call. |
--ignore-upload-errors | Treat network and server errors as warnings (exit 0). Validation, payload-size, and auth errors still fail. |
Function naming
Inventory entries use the same names as oxc-coverage-instrument, byte for byte, so the runtime join works. Fallow picks the name in this order:
- Parent context (method key, variable binding, property key,
export default). - The function's own
id(function foo() {}, named function expression). (anonymous_N)whereNis a file-scoped monotonic counter.
Fallow skips TypeScript declaration files (*.d.ts, *.d.mts, *.d.cts, *.d.tsx) and overload signatures with no body. They never run, so they would always show as untracked in the dashboard.
Path prefix (containerized deployments)
The runtime beacon reports the V8 filePath as V8 sees it at runtime. In a container, that is the path inside the image (for example, /app/src/foo.ts when the Dockerfile sets WORKDIR /app). The static inventory runs in CI on the checkout and gives repo-relative paths (src/foo.ts) by default. The server joins runtime and inventory on the exact (filePath, functionName) pair. If the paths on the two sides have different prefixes, the Untracked filter stays empty and the dashboard shows a mismatched state.
To fix this, pass a --path-prefix that matches your deployed WORKDIR:
fallow coverage upload-inventory --path-prefix /app
Common values by deployment target:
| Target | Prefix |
|---|---|
Docker image with WORKDIR /app (most Node services) | /app |
| Cloud Run / Buildpacks | /workspace |
| Older official Node images | /usr/src/app |
| AWS Lambda | /var/task |
| GitHub Actions default checkout | /home/runner/work/<repo>/<repo> |
After the upload, the server checks up to 100 recent runtime rows and reports their overlap with the inventory. If the overlap is below 50%, the CLI prints a yellow warning with an example mismatch. Until you correct the prefix, the repo detail page in the dashboard shows a configuration-needed state.
Exit codes
| Code | Meaning | When it fires |
|---|---|---|
0 | ok | Upload succeeded, or --dry-run, or --ignore-upload-errors downgraded a transient failure. |
7 | network | DNS, TLS, connect, or transport failure. |
10 | validation | Missing API key, unresolvable project-id, zero functions in walk, or uncommitted changes without --allow-dirty. |
11 | payload too large | Inventory exceeds the server's 200,000-function cap. Scope with --exclude-paths. |
12 | auth rejected | 401 / 403 from the server. Rotate the token or widen its scope. |
13 | server error | 5xx or other non-2xx status, or a malformed response body. |
Example CI job (GitHub Actions)
- name: Upload function inventory to Fallow Cloud
run: |
npx fallow coverage upload-inventory
env:
FALLOW_API_KEY: ${{ secrets.FALLOW_API_KEY }}
Dry-run output
fallow coverage upload-inventory (dry run)
project-id: acme/web
git-sha: a1b2c3d4e5f6...
functions: 14,280
endpoint: https://api.fallow.cloud/v1/coverage/acme/web/inventory
first 5 of 14,280 entries:
src/index.ts:1 bootstrap
src/index.ts:12 (anonymous_1)
src/user.ts:3 createUser
src/user.ts:18 default
src/user.ts:42 (anonymous_5)
... and 14,275 more
upload-source-maps
For bundled code, source maps let Fallow Cloud show runtime coverage on your original source files. The production beacon reports coverage against the deployed bundles. The cloud resolver uses the uploaded source maps to map those bundle positions back to the original source files. fallow coverage upload-source-maps finds the source maps in a build output directory and POSTs each map to Fallow Cloud under the current repo and git SHA.
FALLOW_API_KEY=fallow_live_... \
fallow coverage upload-source-maps --dir dist --repo my-app --git-sha "$GITHUB_SHA"
Source-map defaults
| Flag | Default | Description |
|---|---|---|
--dir <PATH> | dist | Directory that fallow scans recursively. Relative paths resolve from the project root. |
--include <GLOB> | **/*.map | Include glob, matched relative to --dir. |
--exclude <GLOB> | **/node_modules/** | Exclude glob, repeatable. |
--repo <NAME> | package.json repository.url, then git remote get-url origin parsed to owner/repo | Repo identifier in /v1/coverage/:repo/source-maps. Must match the projectId of the beacon (and the --project-id that you pass to upload-inventory). If your beacon reports a bare name, pass --repo <bare-name>. |
--git-sha <SHA> | $GITHUB_SHA, $CI_COMMIT_SHA, $COMMIT_SHA, then git rev-parse HEAD | The commit SHA that the beacon reports for the deployed build. Must be 7-40 hex chars. |
--endpoint <URL> | $FALLOW_API_URL or https://api.fallow.cloud | Override for staging or enterprise deployments. |
--strip-path <BOOL> | true | Upload only the basename as fileName, for example dist/assets/app.js.map -> app.js.map. When runtime coverage reports bundle paths with directories (for example, assets/app.js), set --strip-path=false. |
--dry-run | false | Print what fallow would upload. Does not read FALLOW_API_KEY and makes no network calls. |
--concurrency <N> | 4 | Number of parallel uploads. |
--fail-fast | false | Stop on the first failed map. Without this flag, fallow uploads the rest and reports a partial-failure summary. |
Fallow reads the API key only from FALLOW_API_KEY. There is no --api-key flag on purpose, so secrets do not get into shell history or process lists.
When CI needs a custom PEM trust bundle for Fallow Cloud, set FALLOW_CA_BUNDLE=/path/to/bundle.pem. The bundle replaces the default WebPKI roots. In private-CA environments, pass a complete bundle with the public roots and the private CA. Relative bundle paths resolve from the working directory of the process.
Fallow tries each upload up to three times on network failures, HTTP 429, and HTTP 502/503/504. On HTTP 429, fallow follows Retry-After in delta seconds or as an HTTP date, up to 60 seconds. When fallow cannot set up the HTTP client, or when every map fails with a network error, fallow exits 7. When only some maps fail, or when an upload fails with an HTTP error, fallow exits 1.
Source-map CI snippets
- name: Build
run: npm run build
- name: Upload source maps to fallow
run: npx fallow coverage upload-source-maps --dir dist --git-sha "$GITHUB_SHA"
env:
FALLOW_API_KEY: ${{ secrets.FALLOW_API_KEY }}upload_source_maps:
stage: deploy
script:
- npm run build
- npx fallow coverage upload-source-maps --dir dist --git-sha "$CI_COMMIT_SHA"
variables:
FALLOW_API_KEY: $FALLOW_API_KEY- run: npm run build
- run:
name: Upload source maps to fallow
command: npx fallow coverage upload-source-maps --dir dist --git-sha "$CIRCLE_SHA1"Source-map failure modes
| Exit | Meaning |
|---|---|
0 | All maps uploaded, or dry-run completed. |
1 | Some or all uploads failed, for example because of invalid JSON maps, auth rejection, rate limits, or server errors. |
2 | Bad invocation: missing FALLOW_API_KEY, a repo or SHA that fallow cannot resolve, a missing directory, or zero maps found. |
7 | Network failure: fallow could not set up the HTTP client, or every map failed with a network error after its retries. |