Automatically remove unused exports and dependencies from your codebase. Fallow modifies your source files in place, so treat this like a refactoring tool.
Always commit your changes before running fallow fix. This gives you a clean rollback point if anything unexpected happens.
fallow fix
| Flag | Description |
|---|---|
--dry-run | Preview changes without applying |
--yes, --force | Skip confirmation (required in non-TTY) |
--no-create-config | Refuse to create a new .fallowrc.json when none exists. See Missing config file. |
-f, --format <FORMAT> | Output format: human (default), json |
export keyword is removed, keeping the declaration. For exported enums where the enum itself is never referenced outside its body in the file, the entire enum block is deleted.package.json when they are not imported by another workspaceexport enum X {} shell. Importers in other files then reference a name that no longer exists, so run your TypeScript build to find and clean them up.pnpm-workspace.yaml. See Catalog entries.ignoreExports rule is appended to your fallow config file. Common in shadcn / Radix / bits-ui namespace-barrel codebases where dozens of components/<name>/index.ts files intentionally re-export the same short names. When no fallow config file exists, one is created; see Missing config file.Unused files and class members are not auto-fixed. Use fallow dead-code to find those.
Catalog entries are removed from pnpm-workspace.yaml by line-aware deletion. Object-form entries such as react:\n specifier: ^18.2.0 are removed as one block. When the last entry of a catalog group is removed, the header is rewritten to catalog: {} (or <name>: {}) so the file stays installable; bare catalog: with no value parses as null and pnpm errors at install time. Other comments and stylistic choices in the file are preserved. After a successful catalog edit fallow reminds you to run pnpm install so pnpm-lock.yaml stays in sync.
By default, fallow also removes a contiguous YAML comment block immediately above the entry when it clearly belongs to that entry; configure this with fix.catalog.deletePrecedingComments ("auto", "always", or "never"). Two escape hatches keep curated comments safe regardless of policy: a # fallow-keep marker on any line in the block preserves it (mirrors the inline-suppression convention), and the auto policy additionally preserves section-banner blocks whose comment body starts with three or more =, -, *, _, ~, +, or # characters (e.g. # === React 18 production pins ===). The human-mode summary appends (+M catalog comment lines) to the fixed-issue count when at least one comment line was absorbed.
Entries whose hardcoded_consumers is non-empty (a workspace package still pins a hardcoded version of the same package) are skipped to avoid breaking the next pnpm install; the skip is surfaced on stderr and in the JSON output ({"type": "remove_catalog_entry", "applied": false, "skipped": true, "skip_reason": "hardcoded_consumers", "consumers": [...]}). The JSON action carries both line (first deleted line, the leading comment when policy absorbs one) and entry_line (the catalog entry's original 1-based line) so CI annotators can pick whichever anchor they need. The JSON envelope carries a top-level "skipped" count alongside "total_fixed" for partial-fix gating in CI.
When fallow fix finds duplicate-export issues and no fallow config exists, it creates .fallowrc.json automatically, scaffolded the same way as fallow init (framework and test-runner detection, $schema, entry, ignorePatterns) with the ignoreExports rules layered on top, so the post-fix fallow dead-code does not start surfacing new findings. The created file is reported on stderr:
Created .fallowrc.json with 2 ignoreExports rule(s). Add it to git.
In monorepo subpackages (one of pnpm-workspace.yaml, package.json#workspaces, turbo.json, lerna.json, rush.json above the invocation directory), the create-fallback refuses to avoid fragmenting one config file across N sub-packages. Instead it emits a directive pointing at the workspace root:
Skipped duplicate-export config fix: no fallow config file at packages/ui and the
directory is inside a monorepo (workspace root: /repo). Run `fallow init` at the
workspace root, or invoke `fallow fix` from /repo instead of from a subpackage.
For pre-commit hooks, CI bots, and fallow watch where silently materialising a new top-level file would surprise the user, pass --no-create-config:
# Pre-commit hook
fallow fix --yes --no-create-config
# CI: never create configs as a side effect
fallow fix --yes --no-create-config --format json
The duplicate-export config-add path is skipped with an explanatory message; source-file edits (unused exports, dependencies, enum members, catalog entries) proceed normally.
If a parallel editor save, CI rebase, or other tool changes a source file between analysis and the write, fallow fix skips that file instead of applying stale line offsets. The skip is reported on stderr (Skipping <path>: file content changed since fallow dead-code ran. Re-run fallow fix to refresh the analysis first.) and in the JSON output ({"type": "skipped", "path": "...", "skipped": true, "skip_reason": "content_changed"}). A run with any content-changed skip exits with code 2 so CI does not treat the partial run as a clean no-op. The JSON envelope's top-level skipped_content_changed: number (always present) carries the count separately from skipped (which tallies catalog / YAML guard skips only).
Each per-file rewrite is staged into a sibling temp file, and every stage is promoted to its final path only after all stages succeed. A stage failure (read-only directory, full disk) leaves every target file at its original content. A rename failure mid-batch (rare; the per-file rename is atomic but POSIX has no atomic multi-rename primitive) reports per-path, so a re-run is safe. package.json and pnpm-workspace.yaml are re-read at fix time instead, so a dependency or catalog entry that has already disappeared is a no-op.
Removing an export keyword is only safe when fallow can see every consumer of that symbol. Some consumers are invisible to static analysis: Vitest mock aliases (test.alias pointing at a __mocks__ file), off-workspace end-to-end suites that import shared helpers, and fixture or golden harnesses wired up by a build step. Stripping an export that one of these actually uses turns an analysis false positive into a tsc error and a broken build.
fallow fix therefore withholds export removals in two low-confidence cases:
__mocks__, __fixtures__, fixtures, e2e, e2e-tests, cypress, playwright, examples, evals, or golden (matched on any path segment). Plain test directories (test, tests, __tests__) are intentionally not on the list, because genuinely-dead test helpers are common and should still auto-remove.In both cases the per-file entry is emitted as {"type": "skipped", "path": "...", "skipped": true, "skip_reason": "low_confidence_off_graph"} (or "low_confidence_unresolved_imports"), and the JSON envelope's top-level skipped_low_confidence_exports: number (always present) carries the combined count, disjoint from skipped. Unlike the drift and encoding skips, this is an intentional, conservative decision rather than a recoverable failure: it does not change the exit code. The export is still reported by fallow dead-code, so you can confirm it is truly unused and remove it by hand. High-confidence exports in normal source files are removed.
fallow fix is UTF-8 only. Two encoding shapes are handled explicitly:
UTF-8 BOM round-trip. Files with a leading UTF-8 byte-order mark (EF BB BF, common on Windows-authored TypeScript when Notepad / older VS settings or some IDE plugins are involved) are read with the BOM stripped before line-offset computation and parsing, so reported line numbers do not shift by the BOM codepoint, and the BOM is re-prepended on write so the file's encoding is preserved on round-trip. fallow neither adds nor removes a BOM; if your input has one, the output has one.
Mixed CRLF / LF rejection. Files containing both \r\n and bare-LF line endings (common after cross-platform edits without core.autocrlf) are skipped instead of silently rewritten to the wrong offsets. The stderr message names the remediation:
Skipping src/mixed.ts: file has mixed CRLF/LF line endings.
Normalize with `dos2unix` or set `git config core.autocrlf input`, then re-run `fallow fix`.
The JSON envelope carries a per-file entry ({"type": "skipped", "path": "...", "skipped": true, "skip_reason": "mixed_line_endings"}) and a top-level counter skipped_mixed_line_endings: number (always present) disjoint from skipped_content_changed. Any non-zero mixed-EOL count exits the run with code 2. The skip is not self-healing: re-running fallow fix against the same file produces the same skip; you (or your CI / AI agent) must normalize the file's line endings before fallow can act on it. When the same file carries findings for multiple fixers (an unused export AND an unused enum member), the skip is reported once per file, not once per fixer.
fallow fix --dry-run against duplicate-export findings prints the proposed config write as a unified diff so you can audit the exact bytes that would land before passing --yes. For the create-fallback case the diff shows the full proposed file (BEFORE is empty):
Would create .fallowrc.json with 2 ignoreExports rule(s):
--- .fallowrc.json (does not exist)
+++ .fallowrc.json (proposed)
@@ -0,0 +1,35 @@
+{
+ "$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json",
+ "entry": ["src/index.{ts,tsx,js,jsx}", "src/main.{ts,tsx,js,jsx}"],
+ "ignorePatterns": [".storybook/**"],
+ ...
+ "ignoreExports": [
+ { "file": "src/components/Button/index.ts", "exports": ["*"] },
+ { "file": "src/components/Card/index.ts", "exports": ["*"] }
+ ]
+}
For the edit case (existing config), the diff shows context lines around the proposed additions. JSON output (--dry-run --format json) includes the same diff under a proposed_diff field on the fix entry, plus a created_files: [".fallowrc.json"] array on the create-fallback path so agents can detect file-creation side effects programmatically.
# See what would be removed
fallow fix --dry-run
# JSON output for scripting
fallow fix --dry-run --format jsonWould remove export from src/components/Card/index.ts:1 `CardFooter`
Would remove export from src/providers/trpc-provider/index.tsx:12 `TRPCProvider`
Would remove export from src/server/jobs/queue.ts:61 `enqueueJobDelayed`
Would remove export from src/server/jobs/queue.ts:206 `sweepStuckProcessingJobs`
Would remove export from src/server/jobs/queue.ts:276 `getDeadLetterJobs`
Would remove `@trpc/react-query` from dependencies
6 changes to apply. Proceed? [y/N] y
✓ Applied 6 fixes (5 exports, 1 dependency)