Skip to content

CI: fail on unused code, unused dependencies and broken doc links - #894

Merged
schmitch merged 3 commits into
mainfrom
claude/unused-code-checks
Nov 4, 2026
Merged

schmitch merged 3 commits into
mainfrom
claude/unused-code-checks

Conversation

@schmitch

@schmitch schmitch commented Nov 3, 2026

Copy link
Copy Markdown
Member

Stacked on #890: merge that first; this one is then retargeted to main (do not merge it into #890's branch).

What changed

Three checks catch what rustc, clippy and tsc let through. #890 removed this kind of code by hand; these checks fail CI when more appears.

cargo xtask unused (new xtask/src/unused.rs, run by the fmt job)

  • Reads every workspace member's Cargo.toml and .rs files as text. xtask stays dependency-free.
  • Fails on:
    • a pub item (fn, struct, enum, union, trait, type, const, static) whose name appears nowhere else in the workspace's sources. Definitions inside comments, doc tests and string literals are ignored.
    • a dependency its crate's sources never name (name::…, use name, extern crate name), with - read as _ or dropped (md-5 is md5).
    • a dev-dependency that repeats one of the crate's dependencies with the same or fewer features.
    • a [workspace.dependencies] entry no member inherits.
  • It errs towards silence. Any other mention of a name, including in a comment or a string, counts as a use.
  • A dependency the code needs without naming it (one that only enables a feature of another) can be kept under [package.metadata.unused] ignore = [...] in its crate's manifest. Nothing needs that today.
  • On main before Remove unused code and dependencies #890, it reports exactly the 44 items Remove unused code and dependencies #890 removes (14 pub items, 5 dependencies, 25 dev-dependencies) and nothing else. On Remove unused code and dependencies #890's branch it passes.

pnpm run knip (web job, config in web/knip.jsonc)

  • Fails on a file, an export or a package.json package that nothing imports.
  • Settings:
    • src/routes.ts is an entry, because the router plugin loads it by path.
    • The stylesheets are part of the project, so @import 'tailwindcss' counts as a use and the config needs no ignore list.
    • ignoreExportsUsedInFile is on, so an export that only its own module uses is left alone.
  • knip is pinned at 6.43.0, the newest release outside pnpm's 7-day minimumReleaseAge (6.44.1 is three days old). The lockfile adds knip and its dependencies; every existing package keeps its version, and Vite's entry only gains yaml as a resolved peer.
  • On main before Remove unused code and dependencies #890, it reports the 4 unused files, the 2 devtools packages and the 16 unused exports and types that Remove unused code and dependencies #890 removes. On Remove unused code and dependencies #890's branch it passes.

cargo xtask doc-check (clippy job, last step)

  • Runs cargo doc --workspace --all-features --no-deps --document-private-items with RUSTDOCFLAGS="-D warnings -A rustdoc::private_intra_doc_links".
  • A doc link to a renamed or removed item now fails CI, as does an ambiguous link.
  • Links from public to private items are allowed, because private items are documented too.
  • --all-features brings in forge-e2e, which is entirely behind its feature.
  • It found 23 problems, all fixed in this PR:
    • 17 broken links. Most only needed a path. Three named things that no longer exist: super::OidcProvider (the server builds the ClaimMapping in OidcRuntime::build), the free function authz::can_read (now Authz::can_read), and MockOidc::set_next_id_token_with_retired_key. The doc of MockOidc::rotate_key also described keeping the old signer, which the code doesn't do; it is rewritten to match the code.
    • 4 ambiguous links (format, evaluate, reference), now disambiguated.
    • 2 links with a redundant explicit target.
  • Locally it takes about a minute once the dependencies are built. In CI it runs after cargo sqlx prepare --check, which has already check-built every dependency with every feature.

DEVELOPMENT.md gets a section, "Unused code and stale doc links", covering all three and how to keep a dependency. The new commands are added to cargo xtask help and to the helper-command list.

Tests

Run locally on this branch:

  • cargo test -p xtask: 45 + 2 pass, including 9 new tests for the manifest reader, the source scan and each kind of finding.
  • cargo xtask unused, cargo xtask doc-check, cargo xtask docs --check and cargo xtask changelog --check.
  • cargo fmt --all --check, and cargo clippy -p xtask --all-targets -- -D warnings.
  • cd web && pnpm install --frozen-lockfile && pnpm run knip.

CI does not run on a stacked pull request. It will run once this PR is retargeted to main and pushed again, or by hand with gh workflow run ci.yml --ref claude/unused-code-checks -f base=main.

Changelog

No fragment. Nothing changes for the people who run Forge.

Notes

  • I chose an xtask check over cargo-shear / cargo-machete to keep the repository's checks self-contained and runnable without installing anything. If you would rather use cargo-shear, taiki-e/install-action (already pinned in CI) can install it, and the xtask check could then shrink to the pub-item and repeated dev-dependency parts.
  • None of these checks know whether a comment's prose is still true. The audit behind Remove unused code and dependencies #890 also found stale docs, config examples and duplicated helpers; those still need pull requests of their own.

Generated by Claude Code

Checks for what rustc, clippy and tsc do not report, which the cleanup
before this one removed by hand:

- `cargo xtask unused` (fmt job; xtask stays dependency-free) reads the
  workspace's manifests and sources as text and fails on a `pub` item whose
  name appears nowhere else, a dependency its crate never names
  (`name::…`, `use name`, `extern crate name`; `md-5` is `md5`), a
  dev-dependency that repeats a dependency without adding a feature, and a
  `[workspace.dependencies]` entry no member inherits. A dependency needed
  without being named goes under `[package.metadata.unused] ignore`. On
  `main` before the cleanup it reports the 44 items that were removed.
- `pnpm run knip` (web job) with `web/knip.jsonc`: files, exports and
  packages nothing imports; an export only its own module uses is left
  alone. knip 6.43.0, the newest outside pnpm's 7-day cooldown.
- `cargo xtask doc-check` (clippy job, after the sqlx drift guard): the
  crates' API docs with rustdoc's warnings denied, so a doc link to a
  renamed or removed item fails. It found 23 broken, ambiguous or
  redundant links, fixed here: most needed a path, but `super::OidcProvider`,
  `authz::can_read` and `MockOidc::set_next_id_token_with_retired_key`
  no longer exist, and `rotate_key`'s doc described a snapshot of the old
  signer the code does not take.

DEVELOPMENT.md documents the three under "Unused code and stale doc links".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012e6DJZJ5VT9e32viHMCHoq
Brings in main through #890's branch. Conflict: DEVELOPMENT.md's helper
command list keeps main's `migrate` and `prepare-check` next to `unused` and
`doc-check`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012e6DJZJ5VT9e32viHMCHoq
Base automatically changed from claude/remove-unused-code to main November 4, 2026 06:54
@schmitch
schmitch enabled auto-merge November 4, 2026 06:56
@schmitch
schmitch added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit feb528d Nov 4, 2026
22 checks passed
@schmitch
schmitch deleted the claude/unused-code-checks branch November 4, 2026 07:17
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.

2 participants