Skip to content

CI: flag newly introduced dead code in nuxt-app (fallow) - #256

Draft
claude[bot] wants to merge 1 commit into
mainfrom
claude/fallow-dead-code-ci
Draft

claude[bot] wants to merge 1 commit into
mainfrom
claude/fallow-dead-code-ci

Conversation

@claude

@claude claude Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Requested by Jan Gregor Emge-Triebel · Slack thread

Before: nothing in CI notices when code stops being used. An export whose last caller disappears during a refactor keeps getting formatted, linted, typechecked and built for years, and only turns up when somebody goes looking by hand. Nobody knew how much of it nuxt-app carries — the answer, measured here for the first time, is 26 findings: 2 unreachable files, 4 unused exports, 5 unused types, 4 unused store members, 2 unused component props, 2 import cycles, 6 dependency-hygiene findings and 1 unresolved import.

After: every pull request that touches nuxt-app runs a dead-code pass, and what it finds shows up as annotations on the diff lines themselves. Only findings the pull request introduces count towards the verdict; the 26 that are already in the tree are reported as inherited and gate nothing, so nobody has to clean up first. The step is advisory for now, so a wrong finding costs a scroll past an annotation, not a blocked merge.

How

One step in the existing nuxt-app-test job, straight after npm ci:

- name: Dead-code audit (fallow)
  continue-on-error: true
  run: npx --yes fallow@3.27.0 audit --base "origin/$GITHUB_BASE_REF" --format github-annotations

audit --base, not a baseline file. fallow audit diffs against the PR base and attributes every finding as introduced or inherited, so the gate is "this PR added dead code", not "this repository contains dead code". The alternative — dead-code --save-baseline — would mean a checked-in snapshot of all 26 findings that goes stale on every merge and has to be re-saved by hand. There is nothing to keep in sync here.

continue-on-error: true on purpose. A gate that is red on day one gets switched off rather than used. Remove that line once the 26 existing findings are cleaned up (separate PR — this one changes no application code); from then on a newly introduced dead export fails the job.

fetch-depth: 0 on the checkout of this job. This is the part that actually breaks in CI if you skip it. The audit resolves origin/$GITHUB_BASE_REF...HEAD, and actions/checkout fetches depth 1 of the PR ref only. Reproduced locally against a depth-1 clone:

  • no base branch at all → fatal: ambiguous argument 'origin/main...HEAD': unknown revision, exit 2
  • after a targeted git fetch --depth=1 origin mainfatal: origin/main...HEAD: no merge base, exit 2
  • full history → exit 0

A shallow, targeted fetch would have to guess a depth that always reaches the merge base. The repository is ~11 MB, so fetching all of it is cheaper than being clever.

Pinned version, run through npx, not a devDependency. fallow ships a ~54 MB platform binary plus a ~27 MB type-aware sidecar (~139 MB unpacked). As a devDependency that lands in every npm ci — every local install and every Vercel build — for a tool only CI runs. The trade-off is that Renovate, which is scoped to nuxt-app/**, will not see the pin in the workflow file, so version bumps are a manual one-line edit.

nuxt-app/.fallowrc.json turns off what is noise on this codebase. Every key was verified by running the tool with and without it and comparing the output — fallow rejects unknown keys outright (exit 2), so a typo cannot pass silently:

key effect, measured
ignoreUnresolvedImports + ignoreDependencies for shared-code 38 → 26 findings. shared-code is a Nuxt alias from nuxt.config.ts, which fallow does not read: 11 unresolved imports plus 1 unlisted dependency, all false positives
health.ignore: ["**"] complexity out of the verdict. Without it, adding branches to an existing function fails the audit on a CRAP score; the tree starts with 104 complexity findings across 1286 functions, so that would gate on touching code, not on adding dead code
rules.boundary-violation / policy-violation off drops the two "nothing was measured" diagnostics fallow emits when no boundaries and no rule packs are configured, which is what its own message asks for
rules.unused-class-members off no measurable change today (this tree produces none) — set pre-emptively, because the ones seen during evaluation were all false positives

Duplication needs no key: it is reported but never gates (duplicates.threshold defaults to 0 = no limit), confirmed by running an artificial clone through the audit both ways. Current duplication is 4.4%.

.fallow/ (analysis cache, ~800 KB) is added to nuxt-app/.gitignore. The audit's base snapshots go to /tmp, not into the working tree, so prettier:check and ESLint see nothing new.

Verification

Run locally in nuxt-app, using the exact command from the workflow:

run exit time
current tree vs origin/main 0 — the 26 inherited findings do not block 1.5 s
same, plus a new unused export in a new file 1 — annotated as Unused file, verdict failed 1.5 s
complexity-only change (25 added branches, CRAP 2756) 0 — complexity is not a gate 1.6 s
duplication-only change (2 × 19 duplicated lines) 0 — reported, verdict warned 1.5 s

Analysis itself is ~130 ms; the rest is process start-up. Cold npx download of the pinned version took 4.3 s here, so the CI step should cost well under 30 s. The throwaway commits used for the failing runs are not part of this branch.

prettier:check (exit 0), npm run lint (exit 0, 127 pre-existing warnings) and python -c "yaml.safe_load(...)" on the workflow all still pass, and the Directus test job is untouched.

Not in this PR

  • No application code changes: the 26 existing findings stay as they are.
  • directus-cms is out of scope, as agreed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01G8WfQLANQ82xACc71SosSJ

@vercel

vercel Bot commented Sep 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
programmierbar-website Ready Ready Preview Sep 18, 2026 12:13pm UTC

Request Review

Adds a dead-code step to the existing nuxt-app-test job. `fallow audit --base`
scores only what a pull request introduces, so the 26 findings already in the
tree neither block anyone nor need a baseline file that someone has to keep
current.

The step is `continue-on-error: true` for now: it annotates the diff without
turning the job red while those findings are still there.

The checkout for this job moves to `fetch-depth: 0`, because the audit diffs
`origin/$GITHUB_BASE_REF...HEAD` and a depth-1 checkout has neither the base
branch nor a merge base to diff against.

`.fallowrc.json` silences the `shared-code` alias — declared in nuxt.config.ts,
which fallow does not read, and good for 11 unresolved-import findings plus one
unlisted dependency — and keeps complexity out of the verdict.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G8WfQLANQ82xACc71SosSJ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant