When fallow reports something unexpected, built-in tracing and performance tools help you understand why.
Use --trace FILE:EXPORT to see the full usage chain for a specific export.
fallow dead-code --trace src/utils/format.ts:formatCurrency
UNUSED formatCurrency in src/utils/format.ts
File: reachable
Reason: No references found, export is unused
Prints every file that imports formatCurrency, including indirect usage through barrel re-exports. Use this when:
Use --trace-file PATH to see all incoming and outgoing edges for a file in the module graph.
fallow dead-code --trace-file src/components/Button.tsx
Shows every import the file makes and every file that imports from it. Use this when:
Use --trace-dependency PACKAGE to find everywhere a package is used: imports, script binaries, and plugin detection.
fallow dead-code --trace-dependency lodash
UNUSED moment (0 import(s))
Reports all import sites, package.json script binary usage, and plugin-based detection for the given package. Use this when:
Use dupes --trace FILE:LINE to see all clone instances at a specific source location.
fallow dupes --trace src/utils/validate.ts:42
Shows every other location that shares the same clone group as the code at the given line. Use this when:
Use --performance to get a timing breakdown of each pipeline stage.
fallow dead-code --performance
fallow dupes --performance
┌─ Pipeline Performance ─────────────────────────────
│ discover files: 12ms (847 files)
│ plugins: 2ms
│ parse/extract: 8ms (847 modules, 812 cached, 35 parsed)
│ cache update: 2ms
│ entry points: 1ms (388 entries)
│ resolve imports: 3ms
│ build graph: 1ms
│ analyze: 2ms
│ (other): 1ms
│ ────────────────────────────────────────────────
│ TOTAL: 31ms
└───────────────────────────────────────────────────
The (other) row is the time inside TOTAL not attributed to a named stage (report assembly and inter-stage glue), so the sequential stages plus (other) sum to TOTAL. In combined mode (fallow with no subcommand) the duplication stage runs concurrently with the rest of the pipeline and is marked (concurrent); it is shown for reference but is not part of the TOTAL sum, so it can legitimately exceed TOTAL when duplication detection runs longer than the dead-code pass.
Use this when:
parse/extract runs across all cores. When the parse work was substantial and genuinely parallel, the line gains a (parallel: ~Nms CPU) suffix reporting the summed parse CPU across workers:
│ parse/extract: 382ms (21033 modules) (parallel: ~594ms CPU)
Here the stage finished in 382ms of wall-clock but spent ~594ms of CPU across cores. When that figure is much larger than the wall-clock the stage is CPU-bound and benefits from more cores; when it is close to the wall-clock the stage is I/O-bound and extra threads will not help. The suffix is omitted on warm or trivial runs where there is little real parse work to report.
In combined mode (fallow with no subcommand), the health breakdown reuses the discovered and parsed files from the dead-code pass, so its discover files and parse/extract rows read (measured above) and point you at the Pipeline Performance box where that cost is attributed.
In --format json, the timings carry parse_cpu_ms (summed parse CPU) alongside the wall-clock fields, and the health timings carry shared_parse. These are observational and vary run to run, so do not gate CI on them.
Fallow caches parsed AST data using bincode serialization with xxh3 hashing. On later runs, unchanged files load from cache instead of being re-parsed.
# Run with caching (default)
fallow dead-code
# Skip the cache entirely
fallow dead-code --no-cache
Use --no-cache when:
The --performance flag shows cache statistics, including how many files were cache hits vs. misses. A high hit rate on incremental runs is expected. Only modified files need re-parsing.
Fallow resolves template literals and import.meta.glob, but fully dynamic imports like import(variable) can't be resolved statically.
Fix: Add the target directory to entry in your config:
{
"entry": ["src/plugins/*.ts"]
}
Some frameworks consume exports by naming convention rather than explicit imports. For example, Next.js generateStaticParams or Remix loader functions.
Fix: Fallow's built-in plugins already handle most frameworks. If you're using a niche framework, create a custom plugin or use ignoreExports:
{
"ignoreExports": [
{ "file": "src/routes/**/*.ts", "exports": ["loader", "action"] }
]
}
DI containers (NestJS, Angular, InversifyJS) resolve dependencies at runtime via decorators and metadata. Fallow skips decorated class members by default, but injected services may look unused if they're only referenced via DI tokens.
Fix: Add the DI-registered files as entry points, or suppress specific findings:
// fallow-ignore-next-line unused-export
export class UserService { /* ... */ }
If your codebase uses utility decorators that DO NOT imply reflective use (Playwright @step, internal @measure, @log, etc.), opt them out of the skip via ignoreDecorators so methods carrying ONLY those names are checked for usage normally:
// .fallowrc.json
{ "ignoreDecorators": ["@step"] }
A method carrying any decorator NOT in the list stays skipped, so @step + @Inject keeps the conservative DI-friendly behavior.
Packages listed in peerDependencies or optionalDependencies are not analyzed by default since they may be provided by the consuming project.
Fix: If fallow incorrectly flags these, add them to ignoreDependencies:
{
"ignoreDependencies": ["react", "react-dom"]
}
If a config file (e.g., tailwind.config.ts) is reported as unused, its framework plugin may not be active.
Fix: Run fallow list to check which plugins are active. If the relevant plugin isn't detected, add the config file to entry:
{
"entry": ["tailwind.config.ts"]
}
If a file under a dot-prefixed directory never appears in any finding, discovery did not traverse it. Hidden directories are skipped apart from a few conventional names and the ones an active framework plugin owns. Check the run's diagnostics:
fallow dead-code --format json --quiet | jq '.workspace_diagnostics[] | select(.kind == "skipped-source-dotdir")'
Fix: No config field adds a directory to traversal. Analyze it on its own
with fallow dead-code --root .claude, or add it to ignorePatterns if it is
tool state you never want analyzed. See
Known limitations for the full behavior.