Skip to main content
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.
SeverityDefault
ErrorYes
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.
SeverityDefault
ErrorYes
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.
SeverityDefault
WarningYes
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.
SeverityDefault
ErrorYes
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.
SeverityDefault
WarningYes

Unused optionalDependencies

Same as unused dependencies, but for packages in optionalDependencies. Only reported when the --unused-optional-deps flag is passed.
SeverityDefault
WarningNo (opt-in)

Unused enum members

Enum values declared but never referenced.
SeverityDefault
WarningYes
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.
SeverityDefault
WarningYes
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.
SeverityDefault
ErrorYes
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.
SeverityDefault
ErrorYes
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.
SeverityDefault
WarningYes
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.
SeverityDefault
WarningYes
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 lengthRiskAction
2 (A ↔ B)Moderate, usually a design issueExtract shared code into a third module
3–5Higher, harder to traceBreak the cycle at the weakest edge
5+Architectural issueRefactor 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.
SeverityDefault
WarningYes
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:
  1. Unresolved imports — potential runtime failures
  2. Unlisted dependencies — breaks in strict environments
  3. Unused files — entire modules to delete
  4. Unused dependencies — security and install time
  5. Unused exports — API surface cleanup
  6. Circular dependencies — initialization bugs
  7. Type-only dependencies — install size optimization
  8. Duplicate exports — developer experience
  9. 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:
PatternWhy fallow flags itFix
Scripts run directly (node scripts/seed.ts)Not imported by any moduleAdd to entry in config, or add a package.json script
Dependency injection / runtime wiringRegistered via DI container, not importAdd to entry or use /** @public */
Dynamic require() with variablesPath not statically resolvableAdd the directory to entry
Environment-specific filesOnly imported behind process.env checksAdd to entry if always needed
Unsupported framework conventionsPlugin doesn’t exist for this frameworkAdd 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 pathsimport(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.
Monorepo workspaces, framework conventions (Next.js, NestJS, Angular, etc.), and decorator-driven wiring are handled automatically via auto-detected plugins and workspace detection. See known limitations for edge cases.

JSON _meta object

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.