Skip to content
Fallow home
All docs pages

fallow dead-code

Find the files, exports, dependencies, and types that nothing uses, so you can delete them. Fallow dead-code starts at your entry points, traces reachable exports, and reports the rest.

fallow dead-code finds the code that nothing uses, so you can delete it and keep the codebase smaller. Fallow starts at your entry points, traces all reachable exports, and reports each file, export, dependency, and type that is not used.

Bare fallow runs all analyses (dead code, architecture, duplication, and health). To run only the dead code analysis, use fallow dead-code. For import cycles, boundaries, and policy rules, use fallow architecture. fallow check is still a hidden alias for fallow dead-code.

fallow dead-code
fallow check      # Hidden alias for fallow dead-code

Options

Output

FlagDescription
-f, --format <FORMAT>Output format: human (default), json, sarif, compact, markdown, codeclimate, gitlab-codequality, pr-comment-github, pr-comment-gitlab, review-github, review-gitlab, github-annotations, github-summary
-q, --quietSuppress progress output
--fail-on-issuesExit with code 1 if fallow finds issues
--sarif-file <PATH>Write SARIF output to a file, in addition to --format
--ciCI mode: sets the format to SARIF, turns on fail-on-issues, and hides progress. Individual flags can still override these settings.
--explainAdd metric explanations. In human format, prints a Description: line under each section header. In JSON format, adds a _meta object with metric descriptions and docs links.
--group-by <MODE>Group output by owner (CODEOWNERS), directory (first path component), package (workspace package), or section (GitLab CODEOWNERS [Section] headers, with owners metadata). See global flags.
--summaryIn human output, show only the issue counts per category, without the individual items. JSON output does not change: it always has a summary counts object and the individual items.

Filtering

FlagDescription
--unused-filesReport only unused files
--unused-exportsReport only unused exports
--unused-typesReport only unused types
--private-type-leaksOpt in to private type leak findings (API hygiene) and report only that issue type
--deprecated-exports-in-useOpt in to deprecated exports in use (@deprecated exports that reachable files still use) and report only that issue type
--unused-depsReport only dependency findings: unused dependencies, dev dependencies, and optional dependencies, plus type-only, test-only, and dev-in-production dependencies
--unused-enum-membersReport only unused enum members
--unused-class-membersReport only unused class members
--unused-store-membersReport only unused Pinia store members
--unprovided-injectsReport only unprovided injects
--unrendered-componentsReport only unrendered components
--unused-component-propsReport only unused component props
--absent-component-propsOpt in to absent component props and report only that issue type
--unused-component-emitsReport only unused component emits
--unused-component-inputsReport only unused component inputs
--unused-component-outputsReport only unused component outputs
--unused-svelte-eventsReport only unused Svelte dispatched events
--unused-server-actionsReport only unused server actions
--unused-load-data-keysReport only unused SvelteKit load() data keys
--unused-effect-layersReport only unused Effect layers: layers that only unused layers compose
--unused-tagged-errorsReport only unused tagged errors: Effect tagged errors that no code produces
--unproduced-catch-tagsReport only unproduced catch tags: catchTag handlers for a tag that no code produces
--unresolved-importsReport only unresolved imports
--unlisted-depsReport only unlisted dependencies
--duplicate-exportsReport only duplicate exports
--circular-depsDeprecated alias for fallow architecture --cycles. Report only circular dependencies.
--re-export-cyclesDeprecated alias for fallow architecture --cycles. Report only re-export cycles (barrel files that re-export from each other in a loop, or self-loops). See explanations/dead-code#re-export-cycles
--package-cyclesDeprecated alias for fallow architecture --cycles. Report only package cycles (workspace packages that import each other in a loop). See explanations/dead-code#package-cycles
--boundary-violationsDeprecated alias for fallow architecture --boundaries. Report only boundary violations.
--policy-violationsDeprecated alias for fallow architecture --policy. Report only configured rule-pack violations, including banned usages and gdp-ts proof producers outside allowed files. See Policy violations.
--stale-suppressionsReport only stale suppression comments and @expected-unused JSDoc tags
--unused-catalog-entriesReport only unused pnpm catalog entries
--empty-catalog-groupsReport only empty named pnpm catalog groups
--unresolved-catalog-referencesReport only package references to missing pnpm catalog entries
--unused-dependency-overridesReport only unused pnpm dependency overrides
--misconfigured-dependency-overridesReport only malformed pnpm dependency overrides
--include-dupesAlso run duplication analysis and link its results to dead code findings
--finding-id <ID>Report only the findings with this finding_id. Repeat the flag or pass a comma-separated list. The JSON output adds a finding_id_query answer. A malformed id exits 2. See finding_id_query.

The five deprecated aliases continue to work in version 3.

fallow dead-code continues to report circular dependencies, re-export cycles, package cycles, boundary violations, and policy violations by default until the next major version. When the human output reports such a finding, fallow prints one hint on stderr:

Tip: run `fallow architecture` for import cycles, boundaries and policy rules. A future major version removes them from `fallow dead-code`.

--quiet and the machine formats do not print the hint. The JSON, SARIF, CodeClimate, compact, and markdown output do not change. Use fallow architecture for these findings.

Scoping

FlagDescription
[PATH]Report only findings in this file or directory. Fallow still builds the full project graph. Default: the whole project.
--file <PATH>Scope output to specific files. Accepts multiple values. Fallow reports only issues in these files, and hides project-wide dependency issues. Warns on paths that do not exist. Use it with lint-staged.
--show-cascadeAlso report the unused exports, types, class members, and enum members of unused files. By default, fallow hides them, because deleting the file removes them, and counts them in cascade_hidden. See Findings in unused files.
--include-entry-exportsAlso report unused exports in entry files (package.json main and exports, framework pages, and others). Without this flag, fallow assumes that external code uses them. See global flags.

Type-aware TypeScript evidence

FlagDescription
--type-awareAdd exact evidence from the TypeScript checker for the project questions that fallow answers.
--type-aware-project <PATH>Select a TypeScript project. Repeat the flag for more projects.
--type-aware-require <MODE>best-effort gives advisory partial results. complete fails when the evidence is incomplete.
--symbol-impact <FILE:EXPORT>Return exact consumers, affected files, and targeted tests. Requires --type-aware.

Type-aware TypeScript analysis describes the safety contract, and what fallow checks compared with tsc and Oxlint.

Incremental

FlagDescription
--changed-since <REF>Check only files changed since a git ref
--baseline <PATH>Compare against a previously saved baseline file
--save-baseline <PATH>Save the current results as a baseline file. If the destination has a baseline of another kind, fallow refuses the save with exit 2. A destination with no kind gets one on the next save.
--fail-on-stale-baselineExit 1 when the loaded baseline has an entry that matched nothing in this run. See global flags
--fail-on-baseline-growthExit 1 when the loaded baseline has a key that the same file at the base ref does not have. Set the base ref with --baseline-base <REF>. See Baseline growth gate
--explain-skippedShow which built-in discovery ignore patterns removed source files, with counts
--productionProduction mode (exclude test, story, and dev files)
--top <N>Show only the top N items per issue category in human output

When a quarter or more of the saved baseline entries match no current issue, the run prints a partial-staleness warning on stderr with the command to save the baseline again. health --baseline works the same way. Some runs print no warning, because they cannot judge a whole-project baseline. These are runs narrowed by --file, --changed-since, --diff-file, --workspace, --changed-workspaces, a positional path, --production or an --unused-* filter. --fail-on-stale-baseline turns each stale entry into exit 1.

When the run loaded a baseline, the JSON output of the analysis run has a baseline_staleness object. fallow report --from renders a saved envelope again, and includes the object only if that envelope already had it. The object contains:

  • the counts
  • current_findings
  • change_scoped
  • the advisory result as warning
  • gate_trips, which applies the rule of --fail-on-stale-baseline, so a CI integration reads one boolean and does not need to repeat the rule

Read change_scoped before you divide the counts: a narrowed run can report matched_entries: 0 on a healthy baseline.

scope_reasons names what narrowed the run, so you do not have to guess which input caused it. It is present and not empty exactly when change_scoped is true. dead-code reads the flags and reports diff, changed-since, workspace, changed-workspaces, scope, file, issue-type-filter and production. issue-type-filter is an active --unused-* filter, which drops whole baseline categories before the comparison. Each command reports what it can see, so the names differ per command. Expect new names in the set.

Use scope_reasons to see if a repeat run can remove the scope. production, workspace and changed-workspaces describe what you consider the project to be, so a repeat run keeps them.

A run can load a baseline with entries and use only the inputs that a repeat run can remove: diff, changed-since, scope, file and issue-type-filter. Such a run also adds a recheck-baseline entry to next_steps[] with the unscoped command. That command reads the baseline again and reports. It never saves the baseline again.

  • A run in production mode, or a run scoped to workspaces, adds no entry. These settings can come from the config and the environment as well as from a flag, so the command in the entry would be just as narrow.
  • For the same reason, a diff-scoped run adds no entry while you export FALLOW_DIFF_FILE.
  • A bare fallow run follows the same rule and adds the same entry.

Each command writes its own baseline format, and a saved baseline names that format. Every file that --save-baseline writes has a top-level kind of dead-code, dupes or health. A baseline from an earlier release has no kind and still loads. For such a file, its keys decide the format.

A dead-code baseline also names its key form. --save-baseline writes a top-level "identity": "dc1" and one canonical key for each finding, for example unused-export:src/utils.ts:helper. A canonical key holds the rule, the root-relative path and the names. It never holds a line, a column or a suppression reason, so a line shift does not change the file. The file repeats a key once for each occurrence: N saved occurrences hide at most N current findings with that key.

A dead-code baseline from an earlier release has no identity and still loads. Each old entry matches only its exact old form. Some old forms hold a line, for example the keys of stale suppressions, so these entries can go stale after a line shift. A run that loads an old baseline prints a note on stderr in human output, and sets baseline_staleness.format to legacy in JSON. Neither fails the run. Run --save-baseline once to rewrite the file in the new form. An older fallow version reads a dc1 file without an error, but matches nothing in it, so save the baseline again if you downgrade. A baseline with an identity value that this version does not know exits 2.

fallow dead-code --baseline loads a baseline of another kind as zero entries, so no entry stays unmatched. Such a run prints a warning on stderr, with or without --quiet, that names both kinds and the path. It reports baseline_staleness.unrecognised_format: true and suppresses nothing. Next to it, saved_by names the command that saved the file: dead-code, dupes, or health. The saved_by field is absent for an empty file, for a baseline saved before files named their writer, and for a writer that this version does not know. The field is present only when it is true. An absent field means false. Read this field and not baseline_entries == 0, because a baseline from a project with nothing to record is correctly empty. The baseline gate trips on such a file, so --fail-on-stale-baseline exits 1. A run with no gate turned on keeps its exit code. Invalid JSON still exits 2. A dead-code baseline that has only some of its own keys also exits 2.

Regression detection

FlagDescription
--fail-on-regressionFail if the issue count grows by more than the tolerance, compared with a regression baseline
--tolerance <N>Allowed increase: "2%" (percentage) or "5" (absolute). Default: "0"
--regression-baseline <PATH>Path to regression baseline file (default: .fallow/regression-baseline.json)
--save-regression-baseline <PATH>Save the current issue counts as a regression baseline
Debugging flags
FlagDescription
--trace <FILE:EXPORT>Show where a specific export is used
--trace-file <PATH>Show all edges for a file
--trace-dependency <PACKAGE>Show where a dependency is used
--impact-closure <PATH>Show the files that a change to this file affects, through reverse dependencies and re-export chains
--performanceShow the time of each pipeline stage
# Why is formatDate reported as unused?
fallow dead-code --trace src/utils.ts:formatDate

# What imports/exports does this file have?
fallow dead-code --trace-file src/utils.ts

# Where is lodash used?
fallow dead-code --trace-dependency lodash

# Pipeline performance breakdown
fallow dead-code --performance

Search visibility in traces

A whole-word text search for an export name (grep -w) does not find every consumer. The module graph finds all of them. The JSON output of --trace FILE:NAME tells you which consumers the text search does not find. Each entry in direct_references[] and in direct_references_by_namespace[].references[] has these fields:

FieldDescription
line1-based line of the import statement in the consumer.
search_visibilityfound: the consumer contains the name and uses it under that name. renamed: the consumer contains the name, but uses the export under another local name. missed: the consumer does not contain the name.
reasonWhy the search does not find the uses. See the values below.
search_asThe name to search for in this consumer instead. Absent for found, and absent when no single name finds the uses.

The reason values are:

  • import-alias: the consumer imports the export under another name (import { foo as bar }).
  • default-import: the consumer imports a default export under another name.
  • re-export-alias: a barrel re-exports the export under another name, and the consumer imports that name.
  • namespace-use: the consumer imports the whole namespace (import * as ns) and does not name the export.
  • dynamic-import: the consumer loads the module with import() and does not name the export.
  • pass-through: the consumer only forwards the export and does not use it. The consumer is a barrel (export * from, export { foo } from), or a file that imports the export and only exports it again (import { foo } from './x'; export { foo }, import bar from './x'; export { bar }, import bar from './x'; export default bar, import bar from './x'; export = bar, or a renamed re-export such as import { foo as baz } from './x'; export { baz }). The importers of the forwarding file show in the trace.

A pass-through consumer is found, and it is never a gap. A change of the export needs no edit there. Fallow checks the files that import through it on their own. A file that also uses the binding in another place stays a normal consumer. A .vue, .svelte, .astro, or .mdx file that imports the export also stays a normal consumer, because a template can use the binding.

Example: a consumer has import { formatNumber as utilFormatNumber } from "./utils". A trace of src/utils.ts:formatNumber gives this entry:

{
  "from_file": "src/report.ts",
  "kind": "named import",
  "line": 3,
  "search_visibility": "renamed",
  "reason": "import-alias",
  "search_as": "utilFormatNumber"
}

For a class member trace, each entry in owner_direct_references[] has the same fields. There the check uses the owner export name, not the member name, because a search for the class name finds the users of the class.

The --impact-closure output adds search_visibility, reason, and search_as to each coordination_gap[] entry. The check uses the consumed symbol names. When a consumer uses more than one symbol, the worst level applies: missed, then renamed, then found.

Each such entry also has search_symbol. It names the entry of consumed_symbols that search_visibility, reason, and search_as describe. For a default export, the value is default. The field is present when search_visibility is present. When the consumer uses more than one symbol, the human hint names the symbol, for example [default: name search misses it, search Widget].

Example: src/lib.ts has export default function Thing() and a formatDate export. The consumer src/multi.ts has import Widget, { formatDate } from './lib'. A search for Thing does not find the local name Widget. The --impact-closure src/lib.ts gap for that consumer has these fields (note is left out):

{
  "consumer_file": "src/multi.ts",
  "consumed_symbols": ["default", "formatDate"],
  "search_visibility": "missed",
  "reason": "default-import",
  "search_as": "Widget",
  "search_symbol": "default"
}

Fallow reads the consumer sources when the trace runs. A match in a comment or a string counts as found, because a text search finds it too. The fields are absent when no check applies, for example when fallow cannot read the consumer source. The human output marks missed and renamed references with a short hint. The MCP trace_export and impact_closure tools return the same fields. fallow audit --brief collects the gaps of the changed exports in impact_closure.search_gap. For a .vue, .svelte, .astro, or .mdx file, the declaration ranges come from the script blocks: the SFC <script> blocks, the Astro frontmatter, and the MDX statements. So a change inside a declaration body in such a file counts.

Examples

fallow dead-code

Regression detection

To stop the issue count from growing, save a regression baseline on your main branch. Then compare later runs against it, for example in PR checks.

  1. Save a regression baseline on main:

    fallow dead-code --save-regression-baseline

    This writes the issue counts to .fallow/regression-baseline.json (or a custom path).

  2. On each PR, compare against the baseline:

    fallow dead-code --fail-on-regression

    fallow exits with code 1 if the total issue count is more than the baseline plus the configured tolerance.

  3. Optional: set a tolerance to allow small changes:

    fallow dead-code --fail-on-regression --tolerance 2%   # percentage
    fallow dead-code --fail-on-regression --tolerance 5     # absolute

With --fail-on-regression and --format json, the output can include a regression object:

{
  "regression": {
    "status": "pass",
    "baseline_total": 42,
    "current_total": 45,
    "delta": 3,
    "tolerance": 2.0,
    "tolerance_kind": "percentage",
    "exceeded": false
  }
}
FieldDescription
statuspass, exceeded, or skipped
baseline_totalIssue count from the baseline file
current_totalIssue count from the current run
deltacurrent_total - baseline_total
toleranceConfigured tolerance value
tolerance_kindpercentage or absolute
exceededtrue when the delta is more than the tolerance
reasonPresent only when status is skipped (for example, baseline file not found)

A regression baseline that an older version saved counts the findings in unused files. Fallow now hides these findings, so the baseline allows more new findings than before. Save the regression baseline again with --save-regression-baseline.

These flags also work with fallow dupes and bare fallow (all analyses). The regression check compares the total issue counts of all enabled analyses.

Example output

unused-file:src/server/jobs/worker.ts
unused-file:src/features/savings/hooks/usePotGroups.ts
unused-file:src/server/jobs/cron.ts
unused-export:src/server/jobs/queue.ts:61:enqueueJobDelayed
unused-export:src/server/jobs/queue.ts:206:sweepStuckProcessingJobs
unused-export:src/components/Card/index.ts:1:CardFooter
re-export-cycle:src/api/index.ts:src/api/index.ts <-> src/api/internal/index.ts
re-export-cycle:src/utils/index.ts:src/utils/index.ts (self-loop)
package-cycle:packages/core/src/format.ts:3:@acme/core → @acme/utils → @acme/core

In --format json, re-export cycles have their own field, with the shape of each cycle:

{
  "kind": "dead-code",
  "schema_version": 10,
  "re_export_cycles": [
    {
      "files": ["src/api/index.ts", "src/api/internal/index.ts"],
      "kind": "multi-node",
      "actions": [
        {
          "type": "fix",
          "kind": "refactor-re-export-cycle",
          "auto_fixable": false,
          "description": "Remove one `export * from` (or `export { ... } from`) statement on any one member to break the cycle"
        },
        {
          "type": "suppress-file",
          "kind": "suppress-file",
          "auto_fixable": false,
          "comment": "// fallow-ignore-file re-export-cycle"
        }
      ]
    },
    {
      "files": ["src/utils/index.ts"],
      "kind": "self-loop",
      "actions": [...]
    }
  ]
}

files is sorted lexicographically. The multi-node shape has two or more entries, and the self-loop shape has exactly one. explanations/dead-code#re-export-cycles explains why the cycles have these shapes.

Fix suggestions in JSON output

With --format json, each issue has an actions array with fix and suppress hints that a script or agent can apply.

{
  "path": "src/utils.ts",
  "export_name": "helperFn",
  "line": 10,
  "actions": [
    {
      "type": "remove-export",
      "auto_fixable": true,
      "description": "Remove the unused export from the public API"
    },
    {
      "type": "suppress-line",
      "auto_fixable": false,
      "description": "Suppress with an inline comment above the line",
      "comment": "// fallow-ignore-next-line unused-export"
    }
  ]
}

Each action has these fields:

FieldDescription
typeFix action type in kebab-case (e.g. remove-export, remove-file, suppress-line, add-to-config)
auto_fixabletrue when fallow fix can apply this action automatically. Fallow sets it per finding, not per action type. The same type can be true on one finding and false on another. For example, remove-catalog-entry changes with hardcoded_consumers, and the primary dependency action changes between remove-dependency and move-dependency with used_in_workspaces. Filter on the auto_fixable value of each action, not on type. Auto-fix lists all these per-finding changes.
descriptionA readable explanation of the action
comment(optional) The inline suppression comment to add
note(optional) More context for items that fallow fix cannot fix
config_key(optional) The config key to modify, e.g. "ignoreDependencies" for dependency issues
value(optional) Value to write at config_key. Scalar for keys like ignoreDependencies; array of { file, exports } rule objects for ignoreExports
value_schema(optional) URL of the JSON Schema fragment that describes value. An agent can fetch this schema to validate the payload before it writes the payload into the config of a user
scope(optional) "per-location" on suppress actions that apply to one location of a finding, for example on duplicate_exports (which span more than one file) and stale_suppressions

Re-export findings include a warning note about the effect on the public API. Dependency issues use an add-to-config suppress action (not an inline comment) with the package name and config_key: "ignoreDependencies".

Additional JSON fields

With --format json, the output can include these top-level objects:

entry_points

Counts the resolved entry point files, in total and per source. The module graph starts from these files:

{
  "entry_points": {
    "total": 3,
    "sources": {
      "package.json": 1,
      "plugin": 2
    }
  }
}

cascade_hidden

The number of findings that fallow hid because their file is an unused file. These are unused exports, types, class members, and enum members. Fallow omits the field when the count is 0. With --group-by, the field is at the root of the grouped output, and no group has it. SARIF has the same count in runs[0].properties.cascadeHidden.

{
  "cascade_hidden": 12
}

Pass --show-cascade to list these findings. See Findings in unused files.

summary

The JSON output always includes a summary counts object, with a count for each issue type. This excerpt shows some of the keys:

{
  "summary": {
    "unused_files": 3,
    "unused_exports": 12,
    "unused_types": 2,
    "private_type_leaks": 0,
    "unused_dependencies": 1,
    "unresolved_imports": 0,
    "circular_dependencies": 1,
    "total_issues": 19
  }
}

private_type_leaks is always in the JSON output, so the schema stays stable, but the rule is off by default. Fallow fills it only when you enable it with --private-type-leaks, or set private-type-leaks to warn or error in the configuration.

deprecated_exports_in_use works the same way: the array and the summary.deprecated_exports_in_use count are always in the JSON output. Fallow fills the array only when you enable the rule with --deprecated-exports-in-use or in rules. An unused_exports entry for an export with a @deprecated tag has deprecated: true and, when the tag has a message, deprecated_reason. Both keys are absent when the export is not deprecated.

absent_component_props contains optional props that their component reads but no inspected reachable caller supplies. Fallow omits this array when it is empty; summary.absent_component_props still reports its count. The rule is off by default. Enable it with --absent-component-props, or set absent-component-props to warn or error in rules. These are manual review candidates. See Absent component props for supported syntax and exclusions.

Each entry has component_name, prop_name, and framework to identify the prop. has_default records whether the declaration has a default. path, line, and col point to the declaration. Each entry in inspected_call_sites has path, line, and col for a reachable caller that fallow inspected. Review those callers and the default before proposing a change.

Each finding has an optional effective_severity field (error or warn) with its rule severity, after overrides[].rules. The CI formats read it. See Levels in CI formats.

used_in_workspaces

An unused dependency finding can include used_in_workspaces. This happens when the workspace that declares the package does not use it, but another workspace in the monorepo imports it:

{
  "unused_dependencies": [
    {
      "package_name": "lodash-es",
      "location": "dependencies",
      "path": "packages/shared/package.json",
      "line": 5,
      "used_in_workspaces": ["packages/consumer"]
    }
  ]
}

In human, markdown, GitHub, and GitLab output, this shows as "imported in" or as an "Imported elsewhere" column. The dependency is in the wrong workspace. Move it to the workspace that imports it, and do not remove it automatically.

finding_id

Each dead-code finding has an optional finding_id, stale suppressions included. Use it to track one finding across runs, or to join a finding to its SARIF result, editor diagnostic or CI review thread.

{
  "unused_exports": [
    {
      "path": "src/utils.ts",
      "export_name": "helper",
      "line": 12,
      "finding_id": "dc1:unused-export:81a349a3b9ea3b15"
    }
  ]
}

The form is dc1:<rule>:<16 hex digits>. Fallow computes the id from the rule and the subject of the finding: the root-relative path and the symbol name. The line and the column are not inputs, so these changes keep the id:

  • you add lines above the finding
  • you reformat the file
  • you reorder the declarations

A rename of the file or the symbol gives a new id. Another issue type also gives a new id. When two findings of one type have the same subject, for example a static and an instance member with one name, the first keeps the base id and the next one gets the suffix ~1.

Fallow computes the ids on the full result set, before the filters run. Workspace scope, --changed-since, ignoreFindings and baselines therefore never change the id of a finding that stays in the report. A finding that is absent from a scoped run, or from a run with other config, is unknown in that run. It is not resolved. To ask if a finding still exists, use --finding-id.

The field is optional in the JSON schema, so schema_version does not change. Security findings keep their own finding_id form. See fallow security.

finding_id_query

--finding-id reports only the findings that you ask for. Repeat the flag or pass a comma-separated list:

fallow dead-code --finding-id dc1:unused-export:81a349a3b9ea3b15 --format json --quiet
fallow dead-code --finding-id dc1:unused-file:0123456789abcdef,dc1:unused-export:81a349a3b9ea3b15 --format json --quiet

The filter runs after every other filter and after the baseline, and it does not change the ids. The JSON output then has a finding_id_query object:

{
  "finding_id_query": {
    "requested": ["dc1:unused-file:0123456789abcdef", "dc1:unused-export:81a349a3b9ea3b15"],
    "found": ["dc1:unused-export:81a349a3b9ea3b15"],
    "missing": ["dc1:unused-file:0123456789abcdef"],
    "filtered": [],
    "conclusive": true,
    "inconclusive_reasons": [],
    "analysis_fingerprint": "af1:0123456789abcdef"
  }
}
FieldDescription
requestedThe requested ids, without duplicates, in the order of the arguments
foundThe requested ids that the report contains
missingThe requested ids that the report does not contain
filteredThe requested ids that the analysis found before a filter of this run removed them. These findings still exist.
conclusivetrue when no option of this run can hide a finding that still exists, and no requested id was filtered
inconclusive_reasonsWhy the answer is not conclusive, sorted. Empty exactly when conclusive is true.
analysis_fingerprintA hash (af1:<16 hex digits>) of the inputs that decide which findings the run reports, other than the source code

A missing id means "resolved" only when conclusive is true. The finding is then fixed, suppressed with an inline comment, or ignored by config such as ignoreFindings. When conclusive is false, the state of a missing id is unknown. These options make the answer not conclusive, because each one can hide a finding that still exists:

  • a diff, --changed-since, --workspace, --changed-workspaces, a positional path or --file
  • an issue-type filter such as --unused-exports
  • production mode or includeEntryExports, from the flag or from the config
  • --baseline
  • a rule set to off for the rule of a missing id, in rules or overrides[].rules (rule-off)
  • a requested id in filtered

inconclusive_reasons is an open set of kebab-case names. Read a name that you do not know as "not conclusive". A project with production mode in its config never gets a conclusive answer.

analysis_fingerprint hashes the fallow version, the merged config, the loaded plugins and rule packs, the detection options, the ignore files, the package.json, tsconfig and jsconfig files and the plugin config files (for example vite.config.ts). Store it with your verdict. When a later query gives another fingerprint, treat a missing id as unknown, also when conclusive is true. An edit to a manifest or a tsconfig changes the fingerprint, also when the edit fixes a dependency finding.

The exit code follows the normal rule for the filtered report: 1 when a reported finding has error severity, 0 when every requested id is missing. A malformed id exits 2, so a typo never reads as "resolved". Regression detection and --save-baseline see the findings before the id filter. In human output, a note on stderr gives the counts and says if the answer is conclusive.

The MCP analyze tool (finding_ids) and the Node bindings (findingIds on detectDeadCode) take the same option and return the same finding_id_query.

baseline

With --baseline, a baseline object shows how many entries the baseline file has, and how many of them matched a current issue and were filtered out:

{
  "baseline": {
    "entries": 19,
    "matched": 14
  }
}

The baseline_staleness object has the details. See Incremental.

Unmatched config patterns in workspace_diagnostics

When an ignoreDependencies glob matches no declared dependency, or an ignoreFindings pattern matches no finding, workspace_diagnostics[] has one entry for each such pattern:

{
  "workspace_diagnostics": [
    {
      "path": ".",
      "kind": "ignore-findings-pattern-unmatched",
      "pattern": "src/legcy/**",
      "message": "ignoreFindings pattern 'src/legcy/**' matched no finding in this run, so it has no effect. A pattern is a glob relative to the project root. Fix the pattern, or remove it from the config."
    }
  ]
}

The other output formats name the same patterns: SARIF as toolConfigurationNotifications, Markdown, the job summary, the PR comment and the review summary body as a section, and the other formats as a note on stderr. See Unmatched config patterns.

Stale suppression detection

When you fix an issue, its suppression comment can stay behind. Fallow finds // fallow-ignore comments and /** @expected-unused */ JSDoc tags that no longer match an issue, so you can remove them.

# Only report stale suppressions
fallow dead-code --stale-suppressions

# JSON output includes stale_suppressions array
fallow dead-code --format json --stale-suppressions

Fallow detects two types of stale suppressions:

OriginWhen it's stale
// fallow-ignore-next-line / // fallow-ignore-fileThe suppression no longer matches an issue on the target line or in the file
/** @expected-unused */ JSDoc tagThe tagged export is now imported by another module

The stale-suppressions rule defaults to warn. To enforce it in CI, set it to error:

{
  "rules": {
    "stale-suppressions": "error"
  }
}

With --format json, stale suppressions are in the stale_suppressions array:

{
  "stale_suppressions": [
    {
      "path": "src/utils.ts",
      "line": 5,
      "col": 0,
      "origin": {
        "type": "comment",
        "issue_kind": "unused-export",
        "is_file_level": false
      }
    },
    {
      "path": "src/lib.ts",
      "line": 10,
      "col": 0,
      "origin": {
        "type": "jsdoc_tag",
        "export_name": "createWidget"
      }
    }
  ]
}

Use /** @expected-unused */ and not // fallow-ignore-next-line when you want fallow to tell you if a suppressed export becomes used later. Fallow checks both for staleness, but @expected-unused shows your intent more clearly.

See also