Analyze your project for dead code. Fallow scans entry points, traces all reachable exports, and reports anything that isn't used.
Running bare fallow runs all analyses (dead code, duplication, and health). Use fallow dead-code to run dead code analysis only. fallow check remains as a hidden alias for fallow dead-code.
fallow dead-code
fallow check # Hidden alias for fallow dead-code
| Flag | Description |
|---|---|
-f, --format <FORMAT> | Output format: human (default), json, sarif, compact, markdown, codeclimate, gitlab-codequality, pr-comment-github, pr-comment-gitlab, review-github, review-gitlab |
-q, --quiet | Suppress progress output |
--fail-on-issues | Exit with code 1 if issues are found |
--sarif-file <PATH> | Write SARIF output to a file (in addition to --format) |
--ci | CI mode: sets format to SARIF, enables fail-on-issues, suppresses progress. Individual flags can still override. |
--explain | Add metric explanations. In human format, prints a Description: line under each section header. In JSON format, adds a _meta object with metric descriptions and docs links. |
--group-by <MODE> | Group output by owner (CODEOWNERS), directory (first path component), package (workspace package), or section (GitLab CODEOWNERS [Section] headers, with owners metadata). See global flags. |
--summary | Print a one-line summary of issue counts at the end of the run. In JSON format, adds a summary counts object. |
| Flag | Description |
|---|---|
--unused-files | Only report unused files |
--unused-exports | Only report unused exports |
--unused-types | Only report unused types |
--private-type-leaks | Opt in to private type leak API hygiene findings and only report that issue type |
--unused-deps | Only report unused dependencies |
--unused-optional-deps | Only report unused optional dependencies |
--unused-enum-members | Only report unused enum members |
--unused-class-members | Only report unused class members |
--unused-store-members | Only report unused Pinia store members |
--unresolved-imports | Only report unresolved imports |
--unlisted-deps | Only report unlisted dependencies |
--duplicate-exports | Only report duplicate exports |
--circular-deps | Only report circular dependencies |
--re-export-cycles | Only report re-export cycles (barrel files re-exporting from each other in a loop, or self-loops). See explanations/dead-code#re-export-cycles |
--boundary-violations | Only report boundary violations |
--policy-violations | Only report rule-pack policy violations (banned calls and banned imports declared via the rulePacks config key). See explanations/dead-code#policy-violations |
--stale-suppressions | Only report stale suppression comments and @expected-unused JSDoc tags |
--type-only-deps | Only report type-only dependencies |
--unused-catalog-entries | Only report unused pnpm catalog entries |
--empty-catalog-groups | Only report empty named pnpm catalog groups |
--unresolved-catalog-references | Only report package references to missing pnpm catalog entries |
--unused-dependency-overrides | Only report unused pnpm dependency overrides |
--misconfigured-dependency-overrides | Only report malformed pnpm dependency overrides |
--include-dupes | Also run duplication analysis and cross-reference with dead code |
| Flag | Description |
|---|---|
--file <PATH> | Scope output to specific files. Accepts multiple values. Only issues in the specified files are reported. Project-wide dependency issues are suppressed. Warns on non-existent paths. Useful for lint-staged integration. |
--include-entry-exports | Also report unused exports in entry files (package.json main/exports, framework pages, etc.), which are otherwise assumed to be consumed externally. See global flags. |
| Flag | Description |
|---|---|
--type-aware | Add exact checker-backed evidence for Fallow-owned project questions. |
--type-aware-project <PATH> | Select a TypeScript project explicitly. Repeat for multiple projects. |
--type-aware-require <MODE> | Use best-effort for advisory partial results or complete to fail on incomplete evidence. |
--symbol-impact <FILE:EXPORT> | Return exact consumers, affected files, and targeted tests. Requires --type-aware. |
See Type-aware TypeScript analysis for the safety
contract and the boundary with tsc and Oxlint.
| Flag | Description |
|---|---|
--changed-since <REF> | Only check files changed since a git ref |
--baseline <PATH> | Compare against a previously saved baseline file |
--save-baseline <PATH> | Save current results as a baseline file |
--production | Production mode (exclude test/story/dev files) |
--top <N> | Show only the top N items per issue category in human output |
| Flag | Description |
|---|---|
--fail-on-regression | Fail if issue count increased beyond tolerance vs a regression baseline |
--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 current issue counts as a regression baseline |
| Flag | Description |
|---|---|
--trace <FILE:EXPORT> | Trace usage of a specific export |
--trace-file <PATH> | Show all edges for a file |
--trace-dependency <PACKAGE> | Show where a dependency is used |
--performance | Show pipeline timing breakdown |
# Why is formatDate reported as unused?
fallow dead-code --trace src/utils.ts:formatDate
# What imports/exports does this file have?
fallow dead-code --trace-file src/utils.ts
# Where is lodash used?
fallow dead-code --trace-dependency lodash
# Pipeline performance breakdown
fallow dead-code --performancefallow dead-codeSave a regression baseline on your main branch, then compare on subsequent runs (e.g., in PR checks) to prevent issue count from growing.
Workflow:
Save a regression baseline on main:
fallow dead-code --save-regression-baseline
This writes issue counts to .fallow/regression-baseline.json (or a custom path).
On each PR, compare against the baseline:
fallow dead-code --fail-on-regression
Exits with code 1 if the total issue count exceeds the baseline plus the configured tolerance.
Optionally set a tolerance to allow small fluctuations:
fallow dead-code --fail-on-regression --tolerance 2% # percentage
fallow dead-code --fail-on-regression --tolerance 5 # absolute
When --fail-on-regression is used with --format json, the output includes an optional regression object:
{
"regression": {
"status": "pass",
"baseline_total": 42,
"current_total": 45,
"delta": 3,
"tolerance": 2.0,
"tolerance_kind": "percentage",
"exceeded": false
}
}
| Field | Description |
|---|---|
status | pass, exceeded, or skipped |
baseline_total | Issue count from the baseline file |
current_total | Issue count from the current run |
delta | current_total - baseline_total |
tolerance | Configured tolerance value |
tolerance_kind | percentage or absolute |
exceeded | Whether the delta exceeds the tolerance |
reason | Only present when status is skipped (e.g., baseline file not found) |
These flags also work with fallow dupes and bare fallow (all analyses). The regression check compares total issue counts across all enabled analyses.
unused-file:src/server/jobs/worker.ts
unused-file:src/features/savings/hooks/usePotGroups.ts
unused-file:src/server/jobs/cron.ts
unused-export:src/server/jobs/queue.ts:61:enqueueJobDelayed
unused-export:src/server/jobs/queue.ts:206:sweepStuckProcessingJobs
unused-export:src/components/Card/index.ts:1:CardFooter
re-export-cycle:src/api/index.ts:src/api/index.ts <-> src/api/internal/index.ts
re-export-cycle:src/utils/index.ts:src/utils/index.ts (self-loop)
Re-export cycles ship under their own field in --format json with the cycle's structural shape:
{
"kind": "dead-code",
"schema_version": 9,
"re_export_cycles": [
{
"files": ["src/api/index.ts", "src/api/internal/index.ts"],
"kind": "multi-node",
"actions": [
{
"type": "fix",
"kind": "refactor-re-export-cycle",
"auto_fixable": false,
"description": "Remove one `export * from` (or `export { ... } from`) statement on any one member to break the cycle"
},
{
"type": "suppress-file",
"kind": "suppress-file",
"auto_fixable": false,
"comment": "// fallow-ignore-file re-export-cycle"
}
]
},
{
"files": ["src/utils/index.ts"],
"kind": "self-loop",
"actions": [...]
}
]
}
files is sorted lexicographically; the multi-node shape carries two or more entries, the self-loop shape exactly one. See explanations/dead-code#re-export-cycles for the structural rationale.
When using --format json, every issue includes an actions array with machine-actionable fix and suppress hints.
{
"path": "src/utils.ts",
"export_name": "helperFn",
"line": 10,
"actions": [
{
"type": "remove-export",
"auto_fixable": true,
"description": "Remove the unused export from the public API"
},
{
"type": "suppress-line",
"auto_fixable": false,
"description": "Suppress with an inline comment above the line",
"comment": "// fallow-ignore-next-line unused-export"
}
]
}
Each action has:
| Field | Description |
|---|---|
type | Fix action type in kebab-case (e.g. remove-export, remove-file, suppress-line, add-to-config) |
auto_fixable | true when fallow fix can handle this action automatically. Evaluated per finding, not per action type: the same type may carry true on one finding and false on another (e.g. remove-catalog-entry flips on hardcoded_consumers, the primary dependency action flips between remove-dependency / move-dependency on used_in_workspaces). Filter on this bool of each individual action, not on type. See Auto-fix for the full list of per-instance flips. |
description | Human-readable explanation of the action |
comment | (optional) The inline suppression comment to add |
note | (optional) Additional context for non-auto-fixable items |
config_key | (optional) The config key to modify, e.g. "ignoreDependencies" for dependency issues |
value | (optional) Value to write at config_key. Scalar for keys like ignoreDependencies; array of { file, exports } rule objects for ignoreExports |
value_schema | (optional) URL pointing at the JSON Schema fragment that describes value. Agents that want to validate the payload before writing it into a user's config can fetch the linked schema and apply it |
scope | (optional) Present on duplicate_exports suppress actions as "per-location" since those span multiple files |
Re-export findings include a warning note about public API surface impact. Dependency issues use add-to-config suppress (not inline comments) with the concrete package name and config_key: "ignoreDependencies".
When using --format json, the output may include these additional top-level objects:
entry_pointsLists all resolved entry point files that root the module graph:
{
"entry_points": [
"src/index.ts",
"src/cli.ts",
"src/workers/sync.ts"
]
}
summaryWhen --summary is used, a summary counts object is included:
{
"summary": {
"unused_files": 3,
"unused_exports": 12,
"unused_types": 2,
"private_type_leaks": 0,
"unused_dependencies": 1,
"unresolved_imports": 0,
"circular_dependencies": 1,
"total_issues": 19
}
}
private_type_leaks is present in JSON output for schema stability, but the rule is off by default. It is populated only when enabled with --private-type-leaks or with private-type-leaks set to warn or error in configuration.
used_in_workspacesUnused dependency findings can include used_in_workspaces when a package is unused in the declaring workspace but imported elsewhere in the monorepo:
{
"unused_dependencies": [
{
"package_name": "lodash-es",
"location": "dependencies",
"path": "packages/shared/package.json",
"line": 5,
"used_in_workspaces": ["packages/consumer"]
}
]
}
In human, markdown, GitHub, and GitLab output this appears as "imported in" or an "Imported elsewhere" column. Treat these as workspace placement issues: move the dependency to the consuming workspace instead of auto-removing it.
baseline_deltasWhen --baseline is used, a baseline_deltas object shows changes since the baseline:
{
"baseline_deltas": {
"added": 3,
"removed": 5,
"unchanged": 14
}
}
Fallow detects // fallow-ignore comments and /** @expected-unused */ JSDoc tags that no longer match any issue. This prevents suppression comments from accumulating after the underlying issues are resolved.
# Only report stale suppressions
fallow dead-code --stale-suppressions
# JSON output includes stale_suppressions array
fallow dead-code --format json --stale-suppressions
Two types of staleness are detected:
| Origin | When it's stale |
|---|---|
// fallow-ignore-next-line / // fallow-ignore-file | The suppression no longer matches any issue on the target line or in the file |
/** @expected-unused */ JSDoc tag | The tagged export is now imported by another module |
The stale-suppressions rule defaults to warn. Promote to error for CI enforcement:
{
"rules": {
"stale-suppressions": "error"
}
}
When using --format json, stale suppressions appear in the stale_suppressions array:
{
"stale_suppressions": [
{
"path": "src/utils.ts",
"line": 5,
"col": 0,
"origin": {
"type": "inline_comment",
"issue_type": "unused-export",
"is_file_level": false
}
},
{
"path": "src/lib.ts",
"line": 10,
"col": 0,
"origin": {
"type": "jsdoc_tag",
"export_name": "createWidget"
}
}
]
}
Use /** @expected-unused */ instead of // fallow-ignore-next-line when you want fallow to alert you if a suppressed export later becomes used. Both are tracked for staleness, but @expected-unused communicates intent more clearly.