This page covers what fallow check reports, how to prioritize findings, and when to act.
Pass --explain to any command with --format json to include issue type definitions directly in the JSON output as a _meta object. The MCP server always includes _meta automatically.
Issue types
Fallow detects 13 types of dead code. Each type has a default severity that determines whether it causes a non-zero exit code with --fail-on-issues.
Unused files
Files not reachable from any entry point. No module in the project imports them, and they are not matched by any framework plugin or entry point pattern.
When to act: Almost always. An unused file is dead weight — it adds to IDE indexing time, confuses search results, and may give the impression that code is used when it isn’t.
Common false positives:
- Scripts run directly (e.g.,
node scripts/seed.ts): Add to entry in config, or ensure the script is in a package.json script field (fallow parses those).
- Files loaded by tools fallow doesn’t know about: Add to
entry or the relevant plugin config.
Unused exports
Exported symbols never imported by any other module. The export exists but nothing references it.
When to act: Most unused exports should be removed or unexported. They clutter the public API surface and increase cognitive load when reading the module.
When it’s OK to keep:
- Library public API: Exports consumed by external users fallow can’t see. Mark them with
/** @public */ JSDoc tags, add the library entry point to entry, or use ignoreExports for broad patterns.
- Convention-based exports: Exports consumed by frameworks via naming conventions (e.g., Next.js
getStaticProps). Fallow’s framework plugins handle most of these — if one is missed, configure the plugin or add an ignore pattern.
Unused types
Type aliases and interfaces never referenced by any module.
| Severity | Default |
|---|
| Warning | Yes |
When to act: Remove unused types. They add noise to auto-import suggestions and can mask real API surfaces.
When it’s OK to keep:
- Shared type packages: Types exported for external consumers. Mark the package entry point in
entry.
Unused dependencies
Packages listed in dependencies that are never imported in source code and not used as script binaries in package.json.
When to act: Remove from package.json. Unused dependencies slow down npm install, increase node_modules size, and expand the attack surface for supply chain vulnerabilities.
Common false positives:
- Peer dependencies of other packages: Sometimes a package requires a peer dep that your code doesn’t import directly. Check whether removing it breaks other packages.
- Runtime-only packages (polyfills, CSS frameworks loaded via
@import): Add to ignoreDependencies in config.
Unused devDependencies
Same as unused dependencies, but for packages in devDependencies.
| Severity | Default |
|---|
| Warning | Yes |
Unused optionalDependencies
Same as unused dependencies, but for packages in optionalDependencies. Only reported when the --unused-optional-deps flag is passed.
| Severity | Default |
|---|
| Warning | No (opt-in) |
Unused enum members
Enum values declared but never referenced.
| Severity | Default |
|---|
| Warning | Yes |
When to act: Remove the unused member. Unused enum members inflate serialized values and can cause confusion about which values are valid.
Unused class members
Class methods and properties never referenced outside their class body.
| Severity | Default |
|---|
| Warning | Yes |
When to act: Remove or mark as private. An unreferenced public method signals dead functionality.
Common false positives:
- Framework lifecycle methods (e.g.,
componentDidMount, ngOnInit): Fallow’s framework plugins handle common frameworks. If a lifecycle method is flagged, check the plugin configuration.
- Decorator-driven access (e.g., NestJS
@Get()): Some frameworks use decorators to wire methods at runtime.
Unresolved imports
Import specifiers that cannot be resolved to a file on disk or a node_modules package.
When to act: Always investigate. An unresolved import usually means the code will fail at runtime. Common causes:
- Typo in the import path
- Missing dependency (not installed or not in
package.json)
- Path alias not configured in
tsconfig.json paths
Unlisted dependencies
Packages imported in source code but not declared in package.json.
When to act: Add the package to dependencies or devDependencies. Unlisted dependencies work by accident (hoisted from another package) and will break in environments with strict node_modules resolution (pnpm, yarn PnP).
Duplicate exports
The same symbol name exported from multiple modules. This causes ambiguous auto-imports and makes it unclear which module is the canonical source.
| Severity | Default |
|---|
| Warning | Yes |
When to act: Consolidate to a single canonical export. Re-export from one location if multiple modules need to expose it.
Circular dependencies
Modules that import each other directly or transitively, forming a cycle.
| Severity | Default |
|---|
| Warning | Yes |
When to act: Circular dependencies can cause subtle initialization bugs (accessing a module before it’s fully evaluated). They also make the dependency graph harder to reason about.
| Cycle length | Risk | Action |
|---|
| 2 (A ↔ B) | Moderate, usually a design issue | Extract shared code into a third module |
| 3–5 | Higher, harder to trace | Break the cycle at the weakest edge |
| 5+ | Architectural issue | Refactor module boundaries |
When it’s OK to keep:
- Type-only cycles: If the cycle only involves
import type, there’s no runtime initialization risk. These are reported separately as type-only dependencies.
Type-only dependencies
Production dependencies (dependencies) that are only ever imported via import type. Since type imports are erased at compile time, these packages are never loaded at runtime and should be moved to devDependencies.
| Severity | Default |
|---|
| Warning | Yes |
When to act: Move to devDependencies. This reduces the production install size and clearly communicates that the package is only needed for type checking.
Prioritizing findings
Not all findings are equally important. Use this priority order:
- Unresolved imports — potential runtime failures
- Unlisted dependencies — breaks in strict environments
- Unused files — entire modules to delete
- Unused dependencies — security and install time
- Unused exports — API surface cleanup
- Circular dependencies — initialization bugs
- Type-only dependencies — install size optimization
- Duplicate exports — developer experience
- Unused types / enum members / class members — code hygiene
The first two categories indicate correctness issues that may cause runtime failures. Everything else is maintenance and hygiene.
Reading the summary line
Found 401 issues in 0.16s
The total count includes all issue types. Filter with --unused-files, --unused-exports, etc. to focus on specific categories.
With --fail-on-issues, only issues with error severity cause a non-zero exit. Warnings are reported but don’t fail the build.
With --threshold on specific filters, you can set numeric limits:
Found 401 issues (17 errors, 384 warnings)
Incremental adoption
Most projects have existing dead code. Fallow supports gradual adoption:
Baseline comparison
# Save current state
fallow check --save-baseline .fallow-baseline.json
# Only fail on new issues
fallow check --baseline .fallow-baseline.json --fail-on-issues
The baseline captures issue fingerprints (file + export + type), not line numbers. Refactoring won’t invalidate the baseline as long as the export names stay the same.
Changed-since
# Only check files in the current PR
fallow check --changed-since main
Reports issues only in files that git shows as changed. Fast and focused for CI.
Filter by type
# Start with just unused files
fallow check --unused-files --fail-on-issues
# Add unused exports later
fallow check --unused-files --unused-exports --fail-on-issues
Adopt one issue type at a time. Fix the findings, then add the next type.
Common false positive patterns
Fallow auto-detects 84 frameworks via package.json, so most convention-based exports (Next.js pages, Storybook stories, Jest configs) are handled out of the box. Run fallow list to see which plugins are active and what entry points they add.
False positives that do occur are typically in these categories:
| Pattern | Why fallow flags it | Fix |
|---|
Scripts run directly (node scripts/seed.ts) | Not imported by any module | Add to entry in config, or add a package.json script |
| Dependency injection / runtime wiring | Registered via DI container, not import | Add to entry or use /** @public */ |
Dynamic require() with variables | Path not statically resolvable | Add the directory to entry |
| Environment-specific files | Only imported behind process.env checks | Add to entry if always needed |
| Unsupported framework conventions | Plugin doesn’t exist for this framework | Add entry points manually or write a custom plugin |
Before acting on a finding, verify with --trace. A finding is only as good as fallow’s module graph. If an export is used via a dynamic pattern fallow can’t resolve, use --trace to confirm.
# Verify a specific export
fallow check --trace src/utils.ts:formatDate
# See all edges for a file
fallow check --trace-file src/utils.ts
# Check where a dependency is used
fallow check --trace-dependency lodash
Limitations
- Fully runtime-computed paths —
import(variable) or require(variable) where the argument has no static structure cannot be resolved. Template literals with partial paths (import(\./locales/$.json`)) and import.meta.glob*are* resolved. Only completely opaque variables are a limitation. Mark those directories inentry`.
- Syntactic analysis only — fallow doesn’t invoke the TypeScript compiler. Conditional types or dead code behind generic constraints require type resolution to detect. See known limitations for details.
- Side-effect imports (
import './polyfill') are tracked as file-level edges. The imported file won’t be flagged as unused, but individual exports within it are still analyzed.
When --explain is passed (or via MCP), the JSON output includes a _meta object:
{
"schema_version": 3,
"_meta": {
"docs": "https://docs.fallow.tools/explanations/dead-code",
"issue_types": {
"unused_file": {
"name": "Unused file",
"description": "File not reachable from any entry point",
"severity": "error",
"action": "Delete the file or add it as an entry point"
},
"unused_export": {
"name": "Unused export",
"description": "Exported symbol never imported by another module",
"severity": "error",
"action": "Remove the export keyword or delete the symbol"
},
"circular_dependency": {
"name": "Circular dependency",
"description": "Modules that import each other directly or transitively",
"severity": "warning",
"action": "Extract shared code into a separate module"
}
}
}
}
AI agents and CI systems can use this to interpret issue types without consulting external documentation.