Global flags
Control the project root, config, output format, scope, cache, and CI gates the same way on every fallow command. Reference for the shared CLI flags, including workspace, changed-workspaces, score, trend, and snapshot options.
These flags work the same way across fallow commands, so you learn them once. They set the project root, the config, the output format, the scope, the cache, and the CI gates. Some commands reject a flag that has no meaning for them.
Reference
The --dupes-* flags apply to combined mode. When you do not set one, fallow uses the config value. Each one works like the fallow dupes flag with the same name.
| Flag | Description |
|---|---|
-r, --root <PATH> | Project root directory (default: current working directory) |
-c, --config <PATH> | Path to config file (default: auto-detected) |
--allow-remote-extends | Allow trusted config files to extend HTTPS URLs (default: off, so an HTTPS extends entry exits 2). See fallow guard. |
-f, --format <FORMAT> | Output format: human (default), json, sarif, compact, markdown, codeclimate, gitlab-codequality, pr-comment-github, pr-comment-gitlab, review-github, review-gitlab, badge, github-annotations, github-summary. Alias: --output. --format json is compact by default; add --pretty for indented output. |
--pretty | Indent --format json output so you can read it. JSON is compact by default, so machines and agents get the same schema with less output. Applies only to JSON output. SARIF, Code Climate, baselines, snapshots, caches, and other persisted JSON keep their format with or without this flag. |
-q, --quiet | Suppress progress bars and status messages |
--no-cache | Disable incremental caching and parse all files again |
--threads <N> | Number of parser threads (default: available parallelism) |
--max-file-size <MB> | At discovery, skip source files larger than this many megabytes and do not parse them (default 5; 0 disables the guard). On large repositories, one multi-megabyte generated, vendored, or bundled file can make fallow run out of memory. This guard prevents that. While the guard is on, fallow also skips large one-line minified JS assets. Fallow always analyzes declaration files (.d.ts), for all sizes. Fallow reports skipped files on stderr and in --format json under workspace_diagnostics (kind: "skipped-large-file" or "skipped-minified-file"). You can also set it with FALLOW_MAX_FILE_SIZE. |
--changed-since <REF> (alias: --base) | Report only issues in files changed since this git ref. For this run, the ref replaces the per-package refs of workspaces.changedSince. |
--no-package-baselines | Ignore the per-package refs of workspaces.changedSince for this run, so every workspace package is in full scope. Use it to save or gate a whole-project baseline. --changed-since, --workspace and --diff-file still apply. security, doctor and similar-code reject it. You can also set FALLOW_PACKAGE_BASELINES=false. |
--diff-file <PATH> | Path to a unified diff (for example, git diff --unified=0 main...HEAD). Fallow uses it to report only the source-anchored findings on lines inside an added hunk. It drops a point finding (unused-export, unresolved-import, boundary-violation, and others) when its source line is not in the diff. It drops a range finding (complexity hotspots, clone families) when its [start_line, end_line] does not overlap the diff. Project-level findings (unused deps, catalog entries, dependency overrides) skip this filter. They point at fixed lines in package.json or pnpm-workspace.yaml, and a PR rarely touches those lines, even when the PR caused the finding. To filter these project-level findings too, sticky PR and MR summaries can set FALLOW_SUMMARY_SCOPE=diff. The diff also scopes the runtime-coverage hot-path-touched verdict per line. To read the diff from stdin, pass - as the path or use --diff-stdin (gh pr diff | fallow audit --diff-file -). With --changed-since, the diff controls the line-level filter and --changed-since still scopes file discovery. A one-line note on stderr tells you this. When you omit the flag, fallow uses the FALLOW_DIFF_FILE env var. |
--diff-stdin | Read the unified diff from stdin and not from a file. Equivalent to --diff-file -. Cannot be combined with a --diff-file PATH value other than -. Use it to pipe gh pr diff or git diff directly into fallow. |
--churn-file <PATH> | For projects with no git repository, import change history from a fallow-churn/v1 JSON file and not from git log. Affects only the health signals that use churn. See fallow health › Importing churn from a non-git VCS. |
--baseline <PATH> | Compare against a previously saved baseline file. Only the bare fallow run, dead-code (and check), dupes, and health use it. Other subcommands, decision-surface included, exit 2 with an error that names the subcommands that use the flag. audit has its own baseline flags. |
--baseline-mode <MODE> | How --baseline matches health findings: count (default) matches per file and category, identity matches per function identity. identity works only with a baseline that you saved with --baseline-mode identity. See fallow health › Baseline. |
--save-baseline <PATH> | Save the current results as a baseline file. The same subcommands as --baseline accept it. See Save destinations for the paths that fallow writes to. |
--fail-on-stale-baseline | Exit 1 when a loaded --baseline has an entry that matched nothing in this run. In the bare run, this also checks --dupes-baseline and --health-baseline. Applies to dead-code and check, the bare run, dupes and health, in every output format. It also exits 1 when the loaded file is a baseline of another kind, because such a file suppresses nothing. The result is the exit code and one stderr line with the stale count, the baseline path and the command to save the baseline again. Some runs cannot judge the baseline: a narrowed scope, health --report-only, audit and decision-surface. These runs do not apply the gate. They print a warning on stderr and do not pass silently. A narrowed scope skips only the unmatched-entry check. It still applies the other-kind check, because the scope of the run does not change which command saved the file. The report output does not change. The run always reports the result as baseline_staleness.gate_trips, with or without this flag. That field describes the baseline and not the exit code, so it can be true even when the run did not apply the gate. gate_outcomes["stale-baseline"].status reports fail for both parts of the rule, and stays skipped on audit. |
--fail-on-baseline-growth | Exit 1 when a loaded baseline has a key that the same file at the base ref does not have. This makes a committed baseline shrink-only. See Baseline growth gate. |
--baseline-base <REF> | The git ref that --fail-on-baseline-growth compares the baseline with. Needs --fail-on-baseline-growth. Default: the fallow audit base. |
--fail-on-regression | Fail if the issue count grows by more than the tolerance, compared with a regression baseline. With fallow list --entry-weight, fail when the eager bytes of an entry grow by more than the tolerance. See Startup import weight. |
--tolerance <N> | Allowed increase: "2%" (percentage) or "5" (absolute). Default: "0" |
--regression-baseline <PATH> | Path to regression baseline file (default: .fallow/regression-baseline.json) |
--save-regression-baseline <PATH> | Save the current issue counts as a regression baseline. See Save destinations. |
--explain-skipped | In human and markdown output, show why fallow skipped files. On dupes, it shows a per-pattern count of the files that the default duplicate ignores skipped. On check, dead-code, audit and the default run, it shows the built-in discovery ignore patterns that removed source files, with the count and the directory that lost the most files. Machine formats do not show the note. The counts are always in workspace_diagnostics[] as excluded-by-default-ignore entries. |
--production | Production mode: exclude test, story, and dev files, and use only start and build scripts |
--no-production | Turn production mode off for every analysis, even when the project config sets production: true. Conflicts with --production. |
--production-dead-code | Per-analysis production mode for dead-code in bare combined runs and fallow audit. See Per-analysis production mode. |
--production-health | Per-analysis production mode for health in bare combined runs and fallow audit. |
--production-dupes | Per-analysis production mode for duplication in bare combined runs and fallow audit. |
-w, --workspace <PATTERNS> | Scope output to one or more workspaces. Accepts exact names, globs matched against the package name AND the workspace path (apps/*, @scope/*), and !-prefixed negations (!apps/legacy). Pass comma-separated values (-w web,admin) or repeat the flag. Put quotes around patterns with ! or glob characters, so the shell does not expand them. |
--changed-workspaces <REF> | Monorepo CI scoping from git: scope output to the workspaces that contain a file changed since REF (for example, origin/main, HEAD~1). Fallow gets the workspace set from git diff, so your CI jobs do not need a hand-written --workspace list. Cannot be combined with --workspace. A missing ref or a non-git directory is a hard error (exit 2). Fallow does not fall back to the full scope. A diff that changes only root files maps to 0 workspaces and exits 0 with no issues. |
--performance | Show the time of each pipeline stage |
--explain | In human format, prints a Description: line under each section header. In JSON format, includes metric definitions in the output _meta object. Always enabled for MCP. |
--ci | CI mode: equivalent to --format sarif --fail-on-issues --quiet |
--fail-on-issues | Exit with code 1 if issues are found. Raises warn dead-code findings to error. In health and the bare run, it also raises warn complexity findings. In dupes and in a bare run that analyzes duplication, it also fails the run when at least one clone group remains (the duplication-findings gate). In the bare run, every output format exits 1 when error-severity-findings, health-findings, duplication-threshold or duplication-findings fails, and gate_outcomes marks these entries enforced. audit ignores it. |
--fail-on-parse-error | Exit with code 1 when fallow cannot parse a source file cleanly. Applies to dead-code, health, audit, and the bare run, in every output format. dupes and fix reject the flag. The failOnParseError config key arms the same gate. See Parse error gate. |
--sarif-file <PATH> | Write SARIF output to a file, in addition to the primary --format output. Only the dead-code analysis writes this file, so the flag works with dead-code, security, and a bare run that includes dead-code. dupes, health, and a bare run without dead-code (--skip dead-code, --only dupes, --only health) exit 2. For those, use --format sarif with --output-file. See Save destinations for the paths that fallow writes to. |
-o, --output-file <PATH> | Write the report to a file and not to stdout, for any --format (no ANSI codes). Use it on large projects, where the terminal scrollback cuts off the top of the report. Progress and a Report written to <path> confirmation stay on stderr (suppressed by --quiet). Valid with dead-code, dupes, health, security, and the bare command. Works together with --sarif-file. See Save destinations for the paths that fallow writes to. |
--report-path-prefix <PREFIX> (alias: --annotations-path-prefix) | Override the repository-root offset that fallow adds in front of each path in the CI formats. An empty string disables the offset. See Repository-root paths in CI formats. |
--group-by <MODE> | Group output by CODEOWNERS ownership (owner), first directory component (directory), workspace package (package), or GitLab CODEOWNERS section (section). json, human, sarif and codeclimate show the groups. For dead-code, compact and markdown also show the groups. All other formats render one flat document and print a warning on stderr. See Grouped output. |
--summary | In human output, show only the issue counts per category, without the individual items. JSON output does not change. |
--only <dead-code,dupes,health> | Run only specific analyses when no subcommand is given. check is an alias for dead-code. |
--skip <dead-code,dupes,health> | Skip specific analyses when no subcommand is given |
--coverage <PATH> | In combined mode, give Istanbul coverage data to the health analysis. Same fallback chain as fallow health --coverage. |
--coverage-root <PATH> | Absolute prefix to strip from Istanbul coverage paths in combined mode. See fallow health --coverage-root. |
--dupes-baseline <PATH> | In combined mode, hide the clone groups in a baseline that fallow dupes --save-baseline wrote. With a subcommand, use fallow dupes --baseline. |
--health-baseline <PATH> | In combined mode, hide the complexity findings in a baseline that fallow health --save-baseline wrote. With a subcommand, use fallow health --baseline. |
--dupes-mode <MODE> | Override duplicate detection mode. |
--dupes-near | Include function-scoped near-miss clones. |
--dupes-threshold <PCT> | Override the duplication percentage failure threshold. |
--dupes-min-tokens <N> | Override the minimum token count for clone detection. |
--dupes-min-lines <N> | Override the minimum line count for clone detection. |
--dupes-min-occurrences <N> | Override the minimum clone occurrences (must be >= 2). |
--dupes-skip-local | Report only cross-directory duplicates. |
--dupes-cross-language | Enable TypeScript to JavaScript duplicate matching. |
--dupes-ignore-imports | Exclude module wiring from duplicate detection (default). |
--dupes-no-ignore-imports | Count module wiring as clone candidates. Conflicts with --dupes-ignore-imports. |
--dupes-ignore-symlinks | Omit clone instances whose path is a symlink, or lies under a symlinked directory. See Symlinked files. |
--dupes-no-ignore-symlinks | Report symlinked clone instances, also when the config sets duplicates.ignoreSymlinks: true. Conflicts with --dupes-ignore-symlinks. |
--score | In combined mode, compute the health score (0-100 with letter grade). Enables the health delta header in PR comments. Also available on fallow health. |
--trend | Compare current health metrics against the most recent saved snapshot. Implies --score. Also available on fallow health. |
--save-snapshot [PATH] | Save a vital signs snapshot to track trends. Default path: .fallow/snapshots/<timestamp>.json. Always computes file scores and hotspots. Also available on fallow health. See Save destinations. |
--include-entry-exports | Report unused exports in entry files. Without this flag, fallow marks them as used. Finds typos in framework exports (for example, meatdata instead of metadata). You can also set includeEntryExports: true in your fallow config. When you set the CLI flag, it takes priority. |
--type-aware | Opt in to TypeScript semantic analysis for project-wide symbol evidence. It does not emit compiler diagnostics (default: off, or the typeAware.enabled config value). See Type-aware analysis. |
--no-type-aware | Turn type-aware analysis off, even when typeAware.enabled or FALLOW_TYPE_AWARE turns it on. The run stays fully syntactic. |
--type-aware-project <PATH> | TypeScript project config for type-aware analysis. Repeatable. Default: the typeAware.projects config value, else automatic project selection. |
--type-aware-require <MODE> | best-effort keeps conservative findings and reports semantic gaps. complete fails the quality gate when a semantic query is incomplete. Default: best-effort. |
Save destinations
The save flags (--save-baseline, --save-regression-baseline, --save-snapshot) and the report flags (--output-file, --sarif-file) write only inside these directories:
- the project root
- its Git work tree, when the working directory is also inside that tree
- the CI workspace:
GITHUB_WORKSPACEand GitLabCI_PROJECT_DIR, when they are set RUNNER_TEMP- the system temp directory
Before the analysis runs, fallow resolves the file that each flag writes, and resolves symlinks first. A path outside these directories, for example ../outside.json, exits 2. The check also applies to the default destinations: .fallow/snapshots for a bare --save-snapshot, and the config file for a bare --save-regression-baseline. A committed .fallow or config symlink therefore cannot move the write. A relative path still resolves against the working directory.
The CI workspace keeps a GitHub Action job working that checks out into a subdirectory (actions/checkout with path: app) and sets root: app. The job runs from the workspace, which is outside the Git work tree of the root.
An existing character device or named pipe is always allowed, because a write to it creates no file. So -o /dev/null, --sarif-file /dev/stdout, and process substitution keep working. When stdout is a regular file, /dev/stdout resolves to that file, and the directory rule applies to it. On Windows, NUL writes to the null device, in any case and with an optional colon, and so does \\.\NUL. A name with an extension, such as NUL.txt, stays a normal file name.
Fallow checks the resolved path again right before each write, and does not follow a symlink at the last path component. This makes the window for a swapped path smaller, but does not remove it for an intermediate directory. On Windows, the last check and the open are two steps.
When the default cache directory .fallow resolves outside these directories, for example through a committed symlink, the run does not use the cache and prints one note on stderr. The run does not fail. FALLOW_CACHE_DIR, cache.dir, and --no-cache do not change.
The MCP save_baseline, save_regression_baseline, and save_snapshot parameters return a tool error for the same paths.
Parse error gate
By default, a source file that fallow cannot parse does not change the exit code. The parser also rejects valid syntax that is newer than the parser, so the gate is off by default. To fail the run on such a file, pass --fail-on-parse-error or set failOnParseError: true in the config.
When the gate is on and a file has a source-parse-degraded diagnostic, the run exits 1 in every output format. The gate_outcomes["parse-error"] entry has a files array. Each entry names a file and its parser error count. The human output prints one line per file.
health --report-onlyshows the verdict and does not fail.fallow auditchecks every file that it parsed, not only the changed files. A file that was already unparseable therefore fails every audit with the gate on, also with--gate new-only. The auditverdictdoes not include this gate. Readgate_outcomesfor the exit reason.- The GitHub Action and the GitLab template fail the job when the gate fails. They have no input for this gate: set the config key, or pass the flag in
argsorFALLOW_ARGS.
An older fallow version rejects failOnParseError as an unknown key. When you commit the key, also set minimumVersion.
Baseline growth gate
--baseline and --fail-on-stale-baseline pass when a change adds a finding and saves the baseline again in the same commit. --fail-on-baseline-growth stops this. It compares each loaded baseline with the same file at a base ref:
- A key that the base file does not have is growth. The run exits 1 and lists each new key per category on stderr.
- A renamed file gives a new key, so it counts as growth.
- In a dead-code baseline with line-free keys (
"identity": "dc1"), one more occurrence of a key also counts as growth. A moved line does not. Thedupesandhealthbaseline keys did not change. - A baseline that the base ref does not have is a new baseline. The gate passes and prints a note.
- A base ref that git cannot resolve exits 2, for example in a shallow clone. Fetch the full history (
fetch-depth: 0on GitHub Actions). - A default base that resolves to
HEADexits 2, because a comparison withHEADcannot fail. This happens on a branch that tracks its own pushed copy. Pass--baseline-base origin/mainin CI.
Without --baseline-base, the gate uses the fallow audit base: --changed-since (alias --base), then FALLOW_AUDIT_BASE, then the merge-base with the upstream or the remote default branch.
The gate applies to the dead-code, dupes and health baselines, to the three baselines of the bare run, and to --dead-code-baseline, --dupes-baseline and --health-baseline of fallow audit. health --report-only and the review brief do not run the gate and say so on stderr. A command that loads no baseline rejects the flags.
A commit can rewrite a dead-code baseline from an earlier release into the line-free form. When the base file has no identity and the head file is dc1, the gate translates each old key to its new key and compares by key and count. A commit that removes one finding and adds another in the same rewrite therefore still fails. Some old keys have no safe translation, for example keys with a line or bare package names. The gate compares the categories with such keys by their entry count.
JSON output reports the verdict as gate_outcomes["baseline-growth"]. observed is the number of new keys and threshold is 0.
fallow dead-code --baseline fallow-baselines/dead-code.json \
--fail-on-baseline-growth --baseline-base origin/main
Path scope
To get findings for one file or directory only, pass a path as the first
argument. The bare fallow command and these commands accept it:
check, dupes,
health, audit, security,
fix, list and similar-code.
fallow src/components
fallow health src/api/client.ts
fallow fix src/legacy
Fallow still builds the graph of the whole project, so cross-file facts such as
reachability, re-export chains and importer counts stay correct. Fallow narrows
only the reported findings to the file or directory. fix plans and applies
only the fixes that touch files in scope.
| Behavior | Detail |
|---|---|
| Resolution | Fallow resolves a bare relative path against the project root first. It resolves ./ and ../ against the current working directory. |
| Invalid path | A missing path, or a path outside the project root, exits 2 with an error that tells you what to do. Fallow does not analyze the whole project silently. |
With --workspace | The scope acts as one more workspace root. |
With --changed-since / --diff-file | Fallow reports only files that are in both the scope and the changed-file set. |
audit | The path narrows the set of changed files, so the verdict and the base attribution agree. The base pass stays unscoped, because it runs in another worktree. |
Repository-root paths in CI formats
CI platforms resolve file paths against the repository root. GitHub annotations, CodeClimate reports in GitLab, and inline review comments reject or misplace paths that are relative to another directory.
When --root points at a subdirectory of a git repository (a monorepo package like packages/app/), fallow writes repository-root-relative paths in the CI formats: github-annotations, github-summary, codeclimate, pr-comment-github, pr-comment-gitlab, review-github, and review-gitlab. Fallow detects the offset between --root and the git toplevel and adds it in front of each path automatically, so annotations land on the correct lines.
- In containerized or vendored checkouts, the git toplevel can differ from the CI workspace. Use
--report-path-prefixto override the detected offset. - An empty string (
--report-path-prefix '') disables the offset and gives--root-relative paths again. --annotations-path-prefixis the old name of--report-path-prefix. Fallow still accepts it as an alias, with the same behavior.
For supplied diffs (--diff-file, --diff-stdin, FALLOW_DIFF_FILE), fallow works in the other direction. It detects if the paths in the diff are repository-root-relative or --root-relative, and filters findings to match. A diff that parses but touches no analyzable files (deletion-only, binary-only, or empty) filters the report to zero findings. Only when fallow cannot place the paths of a diff at all does it fall back to the full-scope report, with a warning.
Request outcomes
--changed-since, a supplied diff and --sarif-file ask a run to narrow its report, or to write a file next to it. When the run cannot apply one of them, it continues and the exit code does not change. --format json tells you the result of each request in a root object, keyed by request name. gate_outcomes uses the same shape for the gate results of the run:
{
"kind": "dead-code",
"request_outcomes": {
"changed-since": {
"status": "not-applied",
"affects": "scope",
"requested": "origin/main",
"reason": "not-a-repository",
"message": "--changed-since 'origin/main' was ignored because not a git repository, so this report covers the whole project instead of the changed files. Run fallow from inside the repository, or drop --changed-since."
},
"diff-filter": {
"status": "applied",
"affects": "scope",
"requested": "--diff-file pr.diff",
"scope_size": 18
}
}
}
| Member | Value |
|---|---|
status | applied when the run did what you asked. not-applied when the run could not, and continued. |
affects | scope when the request narrows the report, artifact when it writes a file next to the report. Read this field first, to see the effect of an entry that the run did not apply. On scope, the report is valid and wider than you asked for. On artifact, the run wrote no file, and the scope of the report did not change. |
requested | What you asked for, exactly as you wrote it: the git ref, the diff source label, or the --sarif-file path. Fallow does not change it. Do not join it to the project root, as you do for the other path fields. |
scope_size | How much the request left in scope. Present only when the run applied the request and measured that scope. Each request name has its own unit. diff-filter counts added lines. changed-since counts the changed files that the run analyzed: a changed file that discovery or an ignore rule drops does not count, so a change to a README only gives 0. A combined run counts a changed file that any of its analyses kept. Read the unit from the key of the entry, and do not compare values across names. The count is what the run indexed. diff-filter indexes one million added lines at most, so read a value above zero as a lower bound. 0 means the request left nothing to analyze, so a report with no findings covered nothing. An absent field means the run did not measure the scope. It never means zero. |
reason | A kebab-case token, present exactly when status is not applied. |
message | One sentence that tells you what happened and what to do next. Present exactly when status is not applied. It is the same sentence that the run writes to stderr. |
The object also lists the requests that the run applied. An absent request_outcomes means that the run received no request. It never means that nothing failed.
| Request | Asked by | affects | reason tokens |
|---|---|---|---|
changed-since | --changed-since, --base | scope | git-missing, not-a-repository, git-failed |
diff-filter | --diff-file, --diff-stdin, FALLOW_DIFF_FILE | scope | oversize, unreadable, not-utf8, foreign-namespace, ambiguous-base |
sarif-file | --sarif-file | artifact | directory-create-failed, write-failed, serialize-failed |
Expect new keys, status values and affects values in later releases. Read a name, a status or a class that you do not recognize as one more request and one more outcome. Do not read it as an error, and never as applied or as scope. Select on affects and not on a list of names, because a request added later has its own class. A diff that parsed but names no analyzable file reports applied with scope_size: 0, because the run applied the filter over an empty scope.
A request that the run did not apply also prints a warning on stderr. Fallow records the entry in the envelope with or without --quiet, so a script can rely on the envelope.
fallow audithas norequest_outcomesobject. It exits 2 when its--basedoes not resolve, and it never widens the report.fallow securityrecordssarif-fileasappliedwith the path it wrote. You can then tell a run that wrote the document from a run that asked for none. If the write fails, the run exits 2 with an error document, so no envelope has that entry as not applied.fallow flagsandfallow suppressionshave only thechanged-sinceentry, because neither applies a diff filter.
Formats other than json show the object as one line:
| Format | Request line |
|---|---|
json | The request_outcomes object itself |
github-summary | One Request outcomes: line after the report |
github-annotations | The same line as a ::notice::, before the findings |
pr-comment-github, pr-comment-gitlab, review-github, review-gitlab | The same line, joined into the body's status note |
sarif, codeclimate, badge | Nothing. Read the JSON envelope, or the job summary rendered from it |
A request that narrows the report can apply over an empty scope. The line then adds a clause: the report is clean because nothing in it was analyzable. The clause also tells you to check the diff or the ref that you gave the run.
The GitHub Action and the GitLab template expose the affects: "scope" requests that the run could not apply. The Action uses a job output, and the template writes a dotenv variable. Both also print a warning on an empty scope. See CI integration.
Loaded config disclosure
When fallow loads a config file (auto-discovered or from --config), it prints one line to stderr:
loaded config: /repo/.fallowrc.json
In a monorepo with more than one config, this line tells you which .fallowrc.json fallow uses. Fallow does not print the line when:
--quietis set--formatis anything other thanhuman(json,sarif,compact,markdown,codeclimate,gitlab-codequality,pr-comment-github,pr-comment-gitlab,review-github,review-gitlab,badge,github-annotations,github-summary)
To see the resolved config with extends merged, use the fallow config subcommand.
Examples
# Analyze a project in a different directory
fallow dead-code -r /path/to/project
# Use a specific config file
fallow dead-code -c configs/fallow-strict.json
# JSON output with no progress messages
fallow dead-code -f json --quiet
# Limit parallelism to 4 threads
fallow dead-code --threads 4
# Scope to a single workspace package
fallow dead-code -w @myorg/ui
# Scope to whichever workspaces changed since origin/main (CI primitive)
fallow --changed-workspaces origin/main
# Show timing breakdown
fallow dead-code --performance
# Group issues by CODEOWNERS ownership (see Grouped output below)
fallow dead-code --group-by owner
# Health score in combined mode
fallow --score
# Save a snapshot and track trends
fallow --save-snapshot
fallow --trend
# Let a trusted config file extend an HTTPS URL
fallow dead-code --allow-remote-extends
# Match a health baseline per function, not per file and category
fallow health --save-baseline .fallow/health-baseline.json --baseline-mode identity
fallow health --baseline .fallow/health-baseline.json --baseline-mode identity
# Add TypeScript checker evidence, and fail when it is incomplete
fallow dead-code --type-aware --type-aware-project tsconfig.json --type-aware-require complete
# Keep one run syntactic when the config turns type-aware analysis on
fallow dead-code --no-type-aware
Per-analysis production mode
With the per-analysis production flags, you can turn on production mode for one analysis and keep the other analyses on the full tree. Bare combined runs (fallow with no subcommand) and fallow audit accept three of these flags:
# Treat health as production-only, leave dead-code and dupes in full-tree mode
fallow --production-health
# Audit changed files with production-only health
fallow audit --base main --production-health
The matching environment variables FALLOW_PRODUCTION_DEAD_CODE, FALLOW_PRODUCTION_HEALTH, and FALLOW_PRODUCTION_DUPES accept the same values as FALLOW_PRODUCTION (true, false, 1, 0, yes, no, on, off). The production config field also accepts a per-analysis object:
{
"production": {
"deadCode": false,
"health": true,
"dupes": false
}
}
Fallow uses this order of precedence, from highest to lowest:
- CLI flags (
--production,--production-{dead-code,health,dupes}) - Per-analysis env var (
FALLOW_PRODUCTION_HEALTH, and the others) - Global env var (
FALLOW_PRODUCTION) - Config (
production: { ... }orproduction: true)
The legacy boolean form (--production, production: true, FALLOW_PRODUCTION=true) did not change and still applies to every analysis.
With a subcommand other than audit, fallow rejects the per-analysis CLI flags (for example, fallow dead-code --production-health gives an error). In that case, use the bare command or the per-analysis env vars.
Grouped output
With --group-by, each team or package sees its own issues. The flag splits all issues into labeled groups. It works with the analysis subcommands, and with the formats that can show groups. fallow flags ignores it.
| Format | --group-by |
|---|---|
json | A grouped envelope, shown below |
human | A per-group section |
sarif | properties.group on each result |
codeclimate | A group field on each issue |
compact | For dead-code, each line starts with the group key and a tab. For health and dupes, one flat document and a note on stderr. |
markdown | For dead-code, a heading and a section per group. For health and dupes, one flat document and a note on stderr. |
badge, pr-comment-github, pr-comment-gitlab, review-github, review-gitlab, github-annotations, github-summary | One flat document, and a note on stderr that names the format you passed. The exit code does not change. The comment and review bodies also have a clause with the mode you requested, and that clause names --format json. |
| Mode | Grouping | Requires |
|---|---|---|
owner | CODEOWNERS file ownership. Fallow looks for CODEOWNERS, .github/CODEOWNERS, .gitlab/CODEOWNERS, docs/CODEOWNERS. On a line with more than one owner, the first owner wins. | A CODEOWNERS file (or set codeowners in config) |
directory | First path component (for example, src/utils/foo.ts groups under src) | Nothing |
package | Workspace package name (for example, @myorg/ui). Fallow groups each issue under its package. | A monorepo with workspace packages |
section | GitLab CODEOWNERS [Section] header name. Each section stays in its own group, even when sections share a default reviewer. Teams with one lead reviewer for many sections can then triage per section, and do not get one large group. | A GitLab-style CODEOWNERS file with at least one [Section] header |
With --format json, the output shape changes to a grouped envelope:
{
"kind": "dead-code-grouped",
"grouped_by": "owner",
"total_issues": 12,
"groups": [
{ "key": "@frontend-team", "total_issues": 8, "unused_files": [], "unused_exports": [] },
{ "key": "@backend-team", "total_issues": 4, "unused_exports": [] }
]
}
Each finding goes to the group of its file. A fact about the whole project stays on the root. For example, unused_load_data_keys_global_abstain: true is on the root when the unused-load-data-key rule could not run safely for the project.
The opt-in prop-drilling, thin-wrapper, and duplicate-prop-shape findings are in their groups, but they do not count toward total_issues. In human and markdown output, the group header names them next to the issue count, for example src (0 issues; 3 health signals: 3 duplicate prop shapes).
For --group-by section, each group also has an owners array with the default owners of the section:
{
"kind": "dead-code-grouped",
"grouped_by": "section",
"total_issues": 6,
"groups": [
{
"key": "billing",
"owners": ["@core-reviewers", "@alice", "@bob"],
"total_issues": 2,
"unused_exports": []
},
{
"key": "notifications",
"owners": ["@core-reviewers", "@alice", "@bob"],
"total_issues": 1,
"unused_exports": []
}
]
}
owner puts all sections that share a lead reviewer into one group. section keeps the [Section] headers as group keys, and these keys stay the same when reviewers change.
If your CODEOWNERS file is not in a standard location, set its path in the codeowners field in .fallowrc.json.
Incremental caching
Fallow caches parsed file data between runs, so later runs are faster. On the second run, fallow parses only the changed files and loads the rest from the cache.
The cache works in three steps:
- Fallow hashes each file with xxh3, a fast non-cryptographic hash.
- If the hash matches the cached version, fallow skips the parse.
- Fallow builds the module graph from the cached data and the newly parsed data.
To find the cause of a slow run, use --performance. It shows the cache hit rates and the time of each stage.
fallow dead-code --performance
Discovery 12ms (142 files)
Parsing 48ms (23 parsed, 119 cached)
Resolution 15ms
Analysis 8ms
Total 83ms
To parse all files again, use --no-cache. This helps after large refactors, or when you debug unexpected results.
fallow dead-code --no-cache
To move the persistent cache out of .fallow/cache.bin, set cache.dir in the project config or FALLOW_CACHE_DIR in the environment. When you set both, the env var wins.
FALLOW_CACHE_DIR=/tmp/fallow-cache fallow dead-code