Rules & severity
Adopt fallow in CI one issue type at a time. Set each issue type to error, warn, or off to decide what fails the build.
Rules let you decide which findings fail CI. You set a severity per issue type, so you can adopt fallow one issue type at a time.
Start with all rules as warn. Promote one type at a time to error when you have fixed its findings.
Severity levels
| Level | Behavior |
|---|---|
error | Report and fail CI (non-zero exit code) |
warn | Report but exit 0 |
off | Do not detect or report |
When you do not configure a rule, it uses its default severity. The Default column in Available rule names shows the default of each rule. The defaults by group:
- Default
off(opt-in):private-type-leaks,deprecated-exports-in-use,prop-drilling,thin-wrapper,duplicate-prop-shape,coverage-gaps,feature-flags,require-suppression-reason,security-client-server-leak, andsecurity-sink. - Default
warn:- Dependency hygiene:
unused-dev-dependencies,unused-optional-dependencies,type-only-dependencies,test-only-dependencies, anddev-dependencies-in-production. - Framework components:
unused-store-members,unprovided-injects,unrendered-components,unused-component-props,unused-component-emits,unused-component-inputs,unused-component-outputs,unused-svelte-events,unused-server-actions, andunused-load-data-keys. - Client and server directives:
invalid-client-export,mixed-client-server-barrel, andmisplaced-directive. - Styling:
css-token-drift,css-duplicate-block,css-selector-complexity,css-dead-surface, andcss-broken-reference. - pnpm cleanup:
unused-catalog-entries,empty-catalog-groups, andunused-dependency-overrides. - Other:
re-export-cycle,package-cycle,stale-suppressions, andpolicy-violation.
- Dependency hygiene:
- Default
error: all other rules.
To print the resolved severity of every rule for your project, run fallow config.
In production mode, fallow sets unused-dev-dependencies and unused-optional-dependencies to off.
Configuration
{
"rules": {
"unused-files": "error",
"unused-exports": "warn",
"unused-types": "off",
"private-type-leaks": "off",
"unused-dependencies": "error",
"unused-dev-dependencies": "warn",
"unused-optional-dependencies": "warn",
"unused-enum-members": "warn",
"unused-class-members": "off",
"unresolved-imports": "error",
"unlisted-dependencies": "error",
"duplicate-exports": "warn",
"circular-dependencies": "error",
"re-export-cycle": "warn",
"boundary-violation": "error",
"type-only-dependencies": "error",
"test-only-dependencies": "warn",
"dev-dependencies-in-production": "warn",
"coverage-gaps": "off",
"stale-suppressions": "warn"
}
}Available rule names
| Rule | Default | Description |
|---|---|---|
unused-files | error | Files not reachable from any entry point |
unused-exports | error | Exported symbols never imported |
unused-types | error | Type aliases and interfaces never referenced |
private-type-leaks | off | Opt-in API hygiene check. Finds exported signatures that reference a private type from the same file |
deprecated-exports-in-use | off | Opt-in. An export with a @deprecated JSDoc tag that a reachable file still uses. See Deprecated exports in use |
unused-dependencies | error | Packages in dependencies never used |
unused-dev-dependencies | warn | Packages in devDependencies never used |
unused-optional-dependencies | warn | Packages in optionalDependencies never used |
unused-enum-members | error | Enum values never referenced |
unused-class-members | error | Class members never referenced |
unused-store-members | warn | Pinia store members (state, getters, actions) that no code accesses. Runs only when pinia or @pinia/nuxt is a dependency |
unprovided-injects | warn | inject() or getContext() reads a key that no provide() or setContext() supplies |
unrendered-components | warn | A Vue or Svelte component that a barrel exports but that no code renders |
unused-component-props | warn | A Vue, Svelte, or React component prop that its own component never references. See unusedComponentProps |
unused-component-emits | warn | A Vue <script setup> defineEmits event that its own component never emits |
unused-component-inputs | warn | An Angular @Input(), signal input(), or model() input that its own component never reads |
unused-component-outputs | warn | An Angular @Output() or signal output() output that its own component never emits |
unused-svelte-events | warn | A Svelte createEventDispatcher event that no code in the project listens to |
unused-server-actions | warn | A Next.js Server Action, exported from a "use server" file, that no code references |
unused-load-data-keys | warn | A key of a SvelteKit load() return object that no consumer reads |
prop-drilling | off | A React or Preact prop that passes unchanged through 3 or more components to a distant consumer |
thin-wrapper | off | A React or Preact component whose whole body renders one child with spread props (a candidate for inlining) |
duplicate-prop-shape | off | Three or more React or Preact components, in two or more files, that declare the same set of prop names (a missing shared Props type) |
css-token-drift | warn | A hardcoded styling value in CSS or CSS-in-JS that bypasses the design tokens |
css-duplicate-block | warn | A CSS or CSS-in-JS declaration block that repeats across rules |
css-selector-complexity | warn | A structurally complex CSS selector, nesting depth, or !important use |
css-dead-surface | warn | CSS or CSS-in-JS surface that no code appears to use |
css-broken-reference | warn | A CSS or CSS-in-JS reference that resolves to no stylesheet definition |
unused-catalog-entries | warn | Entries in the catalog: or catalogs: maps of pnpm-workspace.yaml that no workspace package references with the catalog: protocol |
empty-catalog-groups | warn | Named catalogs.<name>: groups in pnpm-workspace.yaml that contain no package entries (top-level catalog: placeholders are ignored) |
unresolved-catalog-references | error | package.json references to catalog: or catalog:<name> where the catalog does not declare the package. pnpm install would fail |
unused-dependency-overrides | warn | Entries in pnpm-workspace.yaml#overrides or package.json#pnpm.overrides for a package that no workspace package declares and that is not in pnpm-lock.yaml. Without a lockfile, fallow checks the manifests only and sets the hint field on these less certain findings |
misconfigured-dependency-overrides | error | pnpm.overrides entries with a key that fallow cannot parse or with no value. pnpm install would fail |
unresolved-imports | error | Imports that fallow cannot resolve |
unlisted-dependencies | error | Imported packages not in package.json |
duplicate-exports | error | Same symbol exported from multiple files |
circular-dependencies | error | Modules that import each other, directly or transitively |
re-export-cycle | warn | Barrel files that re-export from each other in a loop (kind: "multi-node"), or one barrel that re-exports from itself (kind: "self-loop"). A re-export cycle does nothing and is almost always a bug. circular-dependencies covers runtime import loops instead. The cycle spans multiple files, so a per-file overrides.rules.re-export-cycle has no effect. Use the project-wide rules block, or // fallow-ignore-file re-export-cycle on any file in the cycle. Fallow also accepts the aliases re-export-cycles, reexport-cycle, and reexport-cycles. |
package-cycle | warn | Workspace packages that import each other in a loop, so that they cannot be built in dependency order. Built from resolved imports between packages, not from declared dependencies. Imports from test, spec, story, fixture, and tooling config files do not count. Fallow also accepts the alias package-cycles. See Package cycles |
boundary-violation | error | Imports that cross user-defined architecture zone boundaries |
policy-violation | warn | Calls, imports, or catalogue-derived effects that a rule pack bans (the rulePacks config key). The default is warn, so a new pack does not fail CI on its first run. A per-rule severity in the pack overrides this value for each finding, and the exit code uses that per-finding severity. off turns off all rule-pack checks. |
invalid-client-export | warn | A "use client" file that exports a server-only or route-config name |
mixed-client-server-barrel | warn | A barrel that re-exports both a "use client" module and a server-only module |
misplaced-directive | warn | A "use client" or "use server" directive that is not in the leading position and therefore has no effect |
complexity-cyclomatic | error | A function above the cyclomatic ceiling (health.maxCyclomatic or a thresholdOverrides entry). See Complexity rules |
complexity-cognitive | error | A function above the cognitive ceiling (health.maxCognitive or a thresholdOverrides entry) |
complexity-crap | error | A function above the CRAP ceiling (health.maxCrap or a thresholdOverrides entry) |
route-collision | error | Two or more Next.js App Router route files that resolve to the same URL |
dynamic-segment-name-conflict | error | Sibling Next.js dynamic route segments that use different slug names at the same position |
type-only-dependencies | warn | Production dependencies that are only imported with import type |
test-only-dependencies | warn | Production dependencies that only test files import |
dev-dependencies-in-production | warn | Packages in devDependencies that production code imports at runtime. Move them to dependencies |
coverage-gaps | off | Runtime files and exports that no test reaches through the import graph |
feature-flags | off | Detected feature flag patterns. See fallow flags |
security-client-server-leak | off | Client-bound code that reaches a non-public environment variable read. See fallow security. overrides[].rules can set it per path. See Per-path severity |
security-sink | off | Syntactic security sink candidates that need verification. See fallow security. overrides[].rules can set it per path. See Per-path severity |
stale-suppressions | warn | fallow-ignore comments or @expected-unused JSDoc tags that no longer match any issue |
require-suppression-reason | off | Opt-in suppression hygiene check. When you enable it, every fallow-ignore-* comment and @expected-unused tag must end with -- <reason>. Fallow reports a suppression without a reason as missing-suppression-reason |
circular-dependencies and circular-dependency are the same rule. You can use either form in rules, overrides[].rules, and inline directives.
Complexity rules
The rules complexity-cyclomatic, complexity-cognitive, and complexity-crap decide if a complexity finding fails fallow health and fallow audit. The thresholds still decide which functions have a finding. The thresholds are health.maxCyclomatic, health.maxCognitive, health.maxCrap, and health.thresholdOverrides.
| Rule value | Result |
|---|---|
error (default) | The finding fails the run. The exit code and the verdict do not change from earlier releases. |
warn | The finding stays in every output format. fallow health exits 0, and the audit verdict is warn. |
off | Fallow does not report the finding. |
{
"rules": {
"complexity-cyclomatic": "error",
"complexity-cognitive": "warn",
"complexity-crap": "warn"
},
"overrides": [
{ "files": ["src/legacy/**"], "rules": { "complexity-cyclomatic": "warn" } }
]
}
- A function above more than one threshold gets the most severe rule of those kinds. Fallow drops the finding only when all of these rules are
off. overrides[].rulescan set the complexity rules for some files.- The rule applies before
fallow health --min-severity. So awarnfinding never fails the run, also when its band is at or above the--min-severitylevel. --fail-on-issues(and--ci, which sets it) raises awarncomplexity finding toerrorinfallow healthand in the barefallowrun.fallow auditignores--fail-on-issuesand keeps its own verdict.health.maxCrap: 0stays the full off switch for CRAP. It also removes the CRAP signals of the file scores.complexity-crap: "off"hides the findings only, and keeps these signals.- After you set a kind to
off, save the health baseline again. Its entries for the hidden findings no longer match, so they count as stale. - A
thresholdOverridesrow still lists the dimensions that its function breaches, also when a rule hides the finding.
Each complexity finding in the JSON output has an optional effective_severity field (error or warn). The severity field keeps the band (moderate, high, or critical).
A fallow version without these rules warns about an unknown rule name and ignores it. A shared config therefore works with an older binary, but its complexity findings still fail the run there.
Levels in CI formats
The CI formats take the level of each finding from its rule severity. This includes the severity that an overrides[].rules entry sets for the file. For a complexity finding, the complexity-* rule sets the level, not the band. The band stays in the title and the message.
| Rule severity | github-annotations | SARIF | CodeClimate |
|---|---|---|---|
error | ::error | error | major |
warn | ::warning | warning | minor |
With the default rules, a moderate complexity finding is an ::error annotation and a SARIF error, because the finding fails the run. Error annotations come before warnings, so the max-annotations cap of the GitHub Action keeps the errors.
Each dead-code and complexity finding in the JSON output has the optional effective_severity field. fallow report --from reads it, so a saved report gives the same levels as the direct run. A saved report from an older version has no effective_severity. For such a report, report --from uses the earlier levels.
prop-drilling, thin-wrapper, and duplicate-prop-shape findings never fail the run. Their SARIF level is warning at most, also when the rule is error. The human, json, sarif, compact, and markdown reports list these findings, and the editor shows them as hints. The pull request formats (codeclimate, github-annotations, github-summary, and the comment and review formats) do not list them.
Incremental adoption strategy
In an existing codebase, adopt fallow in steps:
- Start permissive. Set all rules to
warnso you see issues without blocking CI. - Fix high-confidence rules first. Promote
unresolved-importsandunlisted-dependenciestoerror. These rules almost never give false positives. - Fix unused files. Promote
unused-filestoerror. You can safely remove unreachable files. - Fix unused exports. This is usually the largest category. Promote
unused-exportstoerrorwhen the count is manageable. - Enable the remaining rules. Promote
unused-types,unused-enum-members, andunused-class-membersas you clean up each category. Enableprivate-type-leaksonly for packages where the public API or the declaration files matter.
Use baselines to enforce rules on new code without failing CI on existing issues.
To treat all warnings as errors for one run, pass --fail-on-issues. The config file stays the same. Use it for pre-merge gates on critical branches. In fallow health and the bare fallow run, it also raises warn complexity findings to error.
fallow dead-code --fail-on-issues
Exit codes
| Code | Meaning |
|---|---|
0 | No error-severity issues |
1 | Error-severity issues found |
2 | Fatal error (invalid config, parse failure) |
When all findings of a run are warn, the run exits 0 and the human summary line shows a yellow ⚠. A red ✗ shows only when a gate fails the run, for example an error finding or --fail-on-issues.
A source file that fallow cannot parse does not change the exit code by default. To fail the run on such a file, see failOnParseError.