fallow fix
Remove unused exports and dependencies without editing each file by hand. Fallow fix edits your source files in place, with a dry-run preview and JSON output.
fallow fix removes unused exports and dependencies for you, so you do not clean them up by hand. Fallow edits your source files in place, so use it as you would use a refactoring tool.
Always commit your changes before you run fallow fix. Then you can roll back if a change is wrong.
fallow fix
Options
| Flag | Description |
|---|---|
--dry-run | Show the changes without applying them |
--yes, --force | Skip the confirmation prompt (required in non-TTY) |
--no-create-config | Refuse to create a new .fallowrc.json when none exists. See Missing config file. |
[PATH] | Fix only the findings in this file or directory. Fallow still builds the full project graph. Default: the whole project. |
-f, --format <FORMAT> | Output format: human (default), json |
What gets fixed
| Issue | What fallow fix does |
|---|---|
| Unused exports | Removes the export keyword and keeps the declaration. For an exported enum that the file never references outside the enum body, it deletes the whole enum block. |
| Unused dependencies | Removes the dependency from package.json when no other workspace imports it. |
| Unused enum members | Removes the member from the enum declaration. When all members of an exported enum are unused, it deletes the whole declaration in one pass, so no empty export enum X {} stays behind. Importers in other files then use a name that no longer exists. Run your TypeScript build to find and fix them. |
| Unused class methods | Removes the method only when a type-aware run (--type-aware or typeAware.enabled) finds no reference to it in the program. Other class members stay. See Incomplete source evidence. |
| Unused pnpm catalog entries | Removes the entry from pnpm-workspace.yaml. See Catalog entries. |
| Duplicate exports | Adds an ignoreExports rule to your fallow config file. This is common in shadcn, Radix, and bits-ui codebases with namespace barrels, where many components/<name>/index.ts files re-export the same short names on purpose. When no fallow config file exists, fallow fix creates one. See Missing config file. |
fallow fix does not fix unused files. Without type-aware analysis, it does not fix unused class members. Use fallow dead-code to find them.
Catalog entries
Fallow deletes catalog entries from pnpm-workspace.yaml line by line and keeps the rest of the file intact.
- Fallow removes an object-form entry, such as
react:\n specifier: ^18.2.0, as one block. - When fallow removes the last entry of a catalog group, it rewrites the header to
catalog: {}(or<name>: {}). This keeps the file installable: a barecatalog:with no value parses as null, and pnpm then fails at install time. - Fallow keeps the other comments and the formatting of the file.
- After a catalog edit, fallow reminds you to run
pnpm install, sopnpm-lock.yamlstays in sync.
By default, fallow also removes a contiguous YAML comment block directly above the entry when the block clearly belongs to that entry. To change this, set fix.catalog.deletePrecedingComments to "auto", "always", or "never". Two exceptions keep your comments safe:
- A
# fallow-keepmarker on any line in the block keeps the block, for all policies. This is the same convention as inline suppression. - The
autopolicy also keeps section-banner blocks. These are comments whose body starts with three or more=,-,*,_,~,+, or#characters (for example,# === React 18 production pins ===).
When fallow removes at least one comment line, the human summary adds (+M catalog comment lines) to the count of fixed issues.
Fallow skips entries with a non-empty hardcoded_consumers, so the next pnpm install does not break. In that case, a workspace package still pins a hardcoded version of the same package. Fallow reports the skip on stderr and in the JSON output ({"type": "remove_catalog_entry", "applied": false, "skipped": true, "skip_reason": "hardcoded_consumers", "consumers": [...]}).
The JSON action has two line fields, so CI annotators can pick the anchor they need:
lineis the first deleted line. When the policy removes a leading comment, this is the comment line.entry_lineis the original 1-based line of the catalog entry.
The JSON envelope has a top-level "skipped" count next to "total_fixed". CI can use it to gate on partial fixes.
Missing config file
When fallow fix finds duplicate-export issues and no fallow config exists, it creates .fallowrc.json. The next fallow dead-code run then does not show new findings. Fallow builds the file the same way as fallow init (framework and test-runner detection, $schema, entry, ignorePatterns) and adds the ignoreExports rules. Fallow reports the new file on stderr:
Created .fallowrc.json with 2 ignoreExports rule(s). Add it to git.
In a monorepo subpackage, fallow fix does not create a config file, so your config does not split across N sub-packages. Fallow treats a directory as a subpackage when one of pnpm-workspace.yaml, package.json#workspaces, turbo.json, lerna.json, or rush.json is above it. Instead, fallow tells you to use 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.
In pre-commit hooks, CI bots, and fallow watch, a new top-level file can be a surprise. To prevent it, 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
Fallow then skips the config change for duplicate exports and prints a message that tells you why. The source-file edits (unused exports, dependencies, enum members, catalog entries) run as usual.
On-disk drift protection
A source file can change between the analysis and the write, for example from an editor save, a CI rebase, or another tool. fallow fix then skips that file, so it does not apply out-of-date line offsets. Fallow reports the skip 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 a content-changed skip exits with code 2, so CI does not treat the partial run as a clean no-op. The JSON envelope always has a top-level skipped_content_changed: number with the count. This count is separate from skipped, which counts only the catalog and YAML guard skips.
Fallow writes each file change to a temporary file next to the target first. It moves the temporary files to their final paths only after all of them are written.
- If a write fails (read-only directory, full disk), all target files keep their original content.
- If a rename fails in the middle of the batch, fallow reports it per path, so you can safely run
fallow fixagain. This is rare: each rename is atomic, but POSIX cannot rename many files atomically. - Fallow reads
package.jsonandpnpm-workspace.yamlagain at fix time. If a dependency or catalog entry is already gone, nothing happens.
Low-confidence export removals
It is safe to remove an export keyword only when fallow can see every consumer of that symbol. Static analysis cannot see some consumers:
- Vitest mock aliases (
test.aliasthat points at a__mocks__file) - end-to-end suites outside the workspace that import shared helpers
- fixture or golden harnesses that a build step connects
If fallow removes an export that one of these uses, a false positive becomes a tsc error and a broken build. For this reason, fallow fix does not remove exports in two low-confidence cases:
- Off-graph consumer directories. The file is under one of
__mocks__,__fixtures__,fixtures,e2e,e2e-tests,cypress,playwright,examples,evals, orgolden(matched on any path segment). Plain test directories (test,tests,__tests__) are not on the list on purpose. Dead test helpers are common, andfallow fixstill removes them. - Files with an unresolved import. The file imports something that fallow could not resolve. Its local usage graph is incomplete, so fallow is less sure that the export is unused.
In both cases, fallow writes the per-file entry as {"type": "skipped", "path": "...", "skipped": true, "skip_reason": "low_confidence_off_graph"} (or "low_confidence_unresolved_imports"). The JSON envelope always has a top-level skipped_low_confidence_exports: number with the combined count, separate from skipped.
This skip does not change the exit code. The drift and encoding skips are recoverable failures, but this skip is a deliberate, conservative choice. fallow dead-code still reports the export, so you can confirm that it is unused and remove it by hand. Fallow still removes high-confidence exports in normal source files.
Incomplete source evidence
Fallow still reports findings with nonempty reachability_caveats, but fallow fix does not remove them. A caveat can come from a parse error, an unreadable file, or a skipped source file, because each of these can hide a reference. workspace_diagnostics shows where the incomplete evidence comes from. Type-aware approval of a class method does not override this guard.
The fix entry has skipped: true and skip_reason: "low_confidence_incomplete_analysis". The JSON envelope has separate counters for skipped_low_confidence_exports, skipped_low_confidence_dependencies, and skipped_low_confidence_members. They are separate from the general skipped counter. These skips do not change the exit code, so check them even when the command succeeds.
File encoding contract
fallow fix supports UTF-8 only. It handles two encoding cases explicitly:
-
UTF-8 BOM round-trip. Some files start with a UTF-8 byte-order mark (
EF BB BF). This is common in TypeScript written on Windows with Notepad, older VS settings, or some IDE plugins. Fallow removes the BOM before it parses the file and computes line offsets, so the reported line numbers do not shift. On write, fallow puts the BOM back, so the file keeps its encoding. Fallow never adds or removes a BOM: if your input has one, the output has one. -
Mixed CRLF / LF rejection. Some files contain both
\r\nand bare-LF line endings. This is common after cross-platform edits withoutcore.autocrlf. Fallow skips these files, so it does not write changes at the wrong offsets. The stderr message tells you how to fix the file: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 has a per-file entry (
{"type": "skipped", "path": "...", "skipped": true, "skip_reason": "mixed_line_endings"}). It always has a top-level counterskipped_mixed_line_endings: number, separate fromskipped_content_changed. If this count is not zero, the run exits with code 2.The skip does not fix itself. When you run
fallow fixagain on the same file, you get the same skip. You, your CI, or your AI agent must normalize the line endings of the file first. When one file has findings for more than one fixer (an unused export AND an unused enum member), fallow reports the skip once per file, not once per fixer.
Dry-run config diff
For duplicate-export findings, fallow fix --dry-run prints the proposed config change as a unified diff. You can check the exact bytes before you pass --yes. When fallow would create a new config file, 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": ["*"] }
+ ]
+}
When a config file already exists, the diff shows context lines around the proposed additions. The JSON output (--dry-run --format json) has the same diff in a proposed_diff field on the fix entry. When fallow would create a config file, the JSON also has a created_files: [".fallowrc.json"] array, so agents can detect that a new file will appear.
Examples
# See what would be removed
fallow fix --dry-run
# JSON output for scripting
fallow fix --dry-run --format jsonExample output
Would 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)