Dead code audit: how Guard proves code is disconnected before proposing removal

A function with no in-repository callers can still be a public API, a framework hook, a migration, or an externally invoked job. Guard therefore starts with supported entry points and protected-use evidence, then separates proven disconnection from uncertainty before any cleanup candidate is prepared.

What question does the audit answer?

The Dead code audit answers one exact question: How much disconnected code, configuration, flags, and metadata slow down humans and agents trying to understand the repository's live behavior?

Disconnected has a narrow meaning here. Guard needs evidence that an item has no credible path from a supported entry point and no documented reason to remain. A compiler warning, an empty reference search, or a test-only caller may start the investigation, but none is deletion proof. The conclusion is fixed to one repository identity, default branch, and full commit; it does not describe every version the project has had or will have.

In the fictional repository used throughout this article, the direct answer is: materially in one former product path. Confirmed Archive Export residue crosses source, configuration, localization, and dependency surfaces. One worker is likely unused, while one registered capability remains ambiguous because its ownership and a dynamic consumer are unverified. That distinction matters more than the raw count of warnings: some candidates support cleanup, while others require an owner decision or are valid protected use.

Dead codeSome issues
59/100
PROBLEMS1
WATCH3
HEALTHY
SKIPPED

Disconnected archive-export residue crosses source, config, localization, and a dependency. One worker is likely unused, while one registered capability still needs an owner decision.

Fictional current result for git.example.test/lighthouse/relay-console. The four chips count summary rows by product tone, not dead-code candidates; Rerun is the only audit action in this card's action rail.

The red outer border reflects the worst visible summary signal, while the 59 score keeps the ring and Some issues badge yellow. The live eye affordance is labeledAudit details. The four chips count summary rows by product tone—one rose and three amber—not the seven candidates discussed in the full fictional report.

What Guard maps before judging reachability

Guard first proves that it can read one authorized repository and resolve the real full commit, default branch, and history depth. Ambiguous identity, failed access, a failed clone, or a missing inventory ends in a diagnostic result rather than a synthetic score. The fictional target is a mixed TypeScript monorepo at commit9f6c2b8d1a4e7c3f5b0d6a2e8c1f4b7d9a3e5c6f, with API, web, worker, and public SDK surfaces.

Repository type changes what counts as a live root. An application normally begins at its bootstraps, routers, and runtime commands. A library or SDK is consumed elsewhere, so documented package entry points, public exports, compatibility aliases, extension points, plugin hooks, generated client methods, and public classes remain supported even when the repository contains no caller. Guard maps the following entry-point families when they exist:

  1. application bootstraps and server starts;
  2. frontend bootstraps and router entry points;
  3. package entry points, public APIs, and documented exports;
  4. CLI commands and documented operational scripts;
  5. routes, controllers, handlers, jobs, workers, queues, schedules, webhooks, and migrations;
  6. deploy, build, and CI wiring that invokes repository behavior;
  7. generated clients together with their code-generation sources;
  8. framework and plugin conventions that create implicit entry points;
  9. documentation and example entry points with an explicit supported purpose.

It also inventories lifecycle commands for setup, build, lint, test, check, generate, run, deploy, migrate, start, demos, and release. Declared variables, configuration, scripts, Make targets, workflows, deploy manifests, localization, and generated metadata are reviewed separately: a declaration can look supported while having no reader or invoker. Dependency directories, build output, caches, coverage, temporary files, and compiled bundles are excluded unless a tracked file is itself relevant evidence.

Fix the revision and map the supported surface

The first pass classifies the repository, finds source and configuration roots, records supported entry points, and discovers useful native commands. A rootGUARD.md can add plausible navigation or ownership context, but it cannot expand permission, suppress evidence, force a score, weaken verification, or change the audit's language and safety rules. Its absence is neutral. No previous audit is read during this pass.

Build reachability and classify current candidates

The second pass traces supported roots into reachable source, configuration, scripts, generated artifacts, and localization. It reviews declared surfaces and major wired capabilities, checks optional tool leads, applies protected-use tests, assigns confidence and cleanup priority, records the eight criterion rows, and calculates current penalties and caps. Previous findings still do not guide collection.

Draft the current answer before comparison

Guard drafts the complete current summary, report, and bounded cleanup context entirely from current-revision evidence. Only after those drafts exist may it validate and read a target-matched previous result. This order keeps an earlier score or candidate list from becoming the assumption that decides what the new audit finds.

Why a tool warning is only a lead

Static analyzers are good at finding cheap starting points. Depending on the stack and repository, Guard may use compiler unused checks, repository linter rules, Knip or ts-prune for JavaScript and TypeScript, Ruff, Vulture, or deptry for Python, Staticcheck for Go, rustc, Clippy, or cargo-udeps for Rust, and comparable JVM, IDE, framework, or repository-native analyzers. This is a menu of possible evidence sources, not a universal scanner bundle that every project must run.

Analysis may require installing project dependencies or preparing an analyzer in the isolated audit environment. That setup remains read-only with respect to repository and external state: no deploys, migrations, seeds, destructive cleanup, production-contacting commands, or intentional upgrades are allowed. Files created by preparation are setup artifacts and cannot become dead-code findings.

A warning advances only after Guard checks the repository type, supported entry points, reachable consumers, dynamic wiring, framework conventions, generated-code markers, public contracts, migrations, feature flags, compatibility paths, documentation, examples, tests, and external invocation conventions. A unit test proves that a worker can execute in isolation; it does not prove the worker has a current product path. An exported method in an internal application does not prove live use, while an exported method in a documented SDK may be the contract itself.

Repository history can explain why a surface exists, but it does not replace current evidence. Telemetry, traffic, route analytics, and feature-flag systems are not default targets. Safely supplied runtime evidence can strengthen a conclusion. When an analyzer or external consumer view is unavailable, Guard names the limitation and lowers confidence where it matters; tool absence alone costs nothing. Repeated warnings do not create repeated findings by volume.

How Guard separates six confidence classes

Confidence is assigned before cleanup priority and before score effect. The percentage is a calibrated judgment inside a class band, not a mathematical probability. Candidates in the same repository are compared so that clearer reachability evidence receives a higher percentage and unresolved protected-use questions lower it. The stored classes areconfirmed, likely_unused, ambiguous,weak_signal, dormant_intentional, andfalse_positive.

  • Confirmed, 85–100%: no credible active entry point remains, protected-use checks were considered, and no reasonable live-use explanation survives. A non-Info case may reduce the score and enter a cleanup handoff.
  • Likely unused, 65–84%: strong evidence points to disconnection, but one reasonable external, dynamic, public-API, or framework uncertainty remains. A bounded non-Info case may reduce the score and enter a handoff.
  • Ambiguous, 35–64%: signals exist, but missing context, dynamic wiring, partial meaningful use, or unclear conventions prevent proof. The case requires manual review and affects the score only when uncertainty materially impairs understanding.
  • Weak signal, 0–34%: a search or tool produced noise and live or protected use looks more likely. It is Info, has zero score effect, and is never queued.
  • Dormant intentional, not a dead-code band: a flag, compatibility path, migration, public contract, or owner note explains why a non-current path remains. It is Info, has zero score effect, and is never queued.
  • False positive, not a dead-code band: valid current or protected use explains the original signal. It is Info, has zero score effect, and is never queued.

The fixture exercises every class. Archive Export andREPORT_CACHE_TTL are confirmed at 94% and 91%. The retry-delivery worker is likely unused at 78%. Admin RPC is ambiguous at 56%; generated billing-client exports are a 28% weak signal; the rollback decoder is intentionally dormant; and the public SDK export is a false positive because package exports and documentation establish an external contract.

Priority answers a different question: how much the candidate affects current understanding and how soon it deserves attention. High covers material confirmed or very likely residue across an active surface; Medium covers bounded residue or an owner check; Low covers isolated cleanup; Info covers protected, weak, intentional, or explanatory cases. Critical is exceptional and requires immediate operational failure or a severe product-decision error. The report keeps class, percentage, priority, score penalty, and product tone separate so confidence is never mistaken for impact.

What belongs in this audit—and what does not

The stored candidate types cover unwired_code,unused_declaration, detached_config, commented_code,obsolete_residue, functionally_irrelevant, dead_dependency_anchor, underused_capability, andother. Each still needs evidence that answers the audit question.

Declared surfaces receive special care. An environment variable, config example, Make target, package script, workflow job, deploy manifest, or generated metadata item enters scope when it exists, looks supported, and has no reader, invoker, or documented external convention. The absence of such a process or tool is neutral. In the fixture,REPORT_CACHE_TTL is detached because templates declare it but the source and config loaders never read it, so its declared-surface class is detached. Admin RPC is different: its service and health operation are live, while the use and ownership of other operations remain unclear. Its capability class is wired_underused, which makes it report-only context rather than deletion proof. The public SDK islive because its external contract is documented.

Before any class is assigned, Guard checks protected-use families:

  • public APIs, SDK exports, package entry points, plugin hooks, extension points, framework conventions, and documented compatibility aliases;
  • migrations, data backfills, rollback paths, compatibility shims, legacy protocol handlers, and active deprecation paths;
  • intentionally dormant feature-flagged code with an owner, purpose, or review point;
  • generated, vendored, or framework-managed files with clear source markers;
  • examples, documentation snippets, seed data, stories, demos, and fixtures with an explicit validation or documentation purpose;
  • externally invoked jobs, scripts, webhooks, and CLI commands whose callers may not appear in the repository;
  • dynamic dispatch, reflection, registration conventions, generated service definitions, and external consumers that static import graphs can miss.

Protected does not mean permanently exempt. It means the report must preserve the reason the surface remains and cannot turn an absence of local callers into a removal claim.

Boundary with Codebase Hygiene

Codebase Hygiene owns broad maintenance drag: tracked clutter, duplication, hardcoded values, oversized mixed-responsibility modules, weak maintenance commands, and general unused or commented-code smells. The Dead code audit owns supported-entry-point mapping, repository-type-aware reachability, protected-use review, calibrated confidence classes, wholly detached operational surfaces, and bounded removal candidates. A smell becomes deletion evidence only after this proof model supports it.

Boundary with Configuration Hygiene

Dead Code owns a whole detached configuration or operational surface that looks supported but has no current reader or invoker. Configuration Hygiene owns the contract inside an otherwise live surface: defaults, validation, documentation, duplication, wiring that reads but has no effect, and whether a live option remains justified. Those per-option quality issues are not double-scored here.

Boundary with Dependency Hygiene

Dead Code owns a dependency anchor only when dead or unwired code is the sole reason the dependency, asset, generated client, or build step remains. Dependency Hygiene owns the dependency itself: resolved versions, advisories, runtime or build scope, reproducibility, acquisition paths, lifecycle controls, footprint, and update safety. The archive SDK can be an anchor here without being labeled stale, vulnerable, or unsafe.

The eight scoring criteria

Every successful audit records eight ordered 0–5 rows as evidence labels. Five means the category is clean or has no meaningful candidate. Four means only small isolated traces remain. Three marks bounded residue or ambiguity worth addressing. Two means material residue or uncertainty in an active area. One marks high-impact or repeated residue that makes current behavior hard to understand. Zero is reserved for systemic residue or ambiguity so broad that the behavior cannot be understood confidently.

The status icon follows only from that row score: ✅ for 4–5, ⚠️ for 2–3, and ❌ for 0–1. It does not come from cleanup priority. Most importantly, the eight values are not added, averaged, weighted, or scaled into the final 0–100 score. They give readers a consistent category view; the final score uses the separate penalties and caps explained next.

  1. 1

    Unwired code

    Guard looks for a credible path from supported application, route, command, job, public API, framework, or operational entry points. In the example, the retry-delivery worker scores 3 because it is absent from the worker registry but an external name-based invocation remains possible.

  2. 2

    Unused declarations

    Imports, variables, parameters, private methods and classes, exports, types, labels, macros, and equivalent declarations belong here only after protected-use review. The example scores 5 because generated-client and public-SDK warnings were explained rather than promoted to findings.

  3. 3

    Detached config, flags, parameters, or metadata

    An existing declaration that looks supported but is not read, invoked, or honored can misdescribe live behavior. REPORT_CACHE_TTL has no reader, while the archive example config is evidence for the broader archive root cause, so this row scores 2.

  4. 4

    Commented or copied implementation

    Disabled blocks, backup implementations, and old copies near active logic are assessed as residue when they have no supported purpose. No meaningful cluster survives in the example, so the row scores 5.

  5. 5

    Obsolete feature residue

    A removed product path can leave source, routes, adapters, translations, documentation, schemas, configuration, flags, clients, and scripts behind. Archive Export crosses four such surfaces and makes this the weakest row, at 1.

  6. 6

    Functionally irrelevant code

    Some code executes but its result is ignored, overwritten, unread, or behaviorally inert. Guard found no material example at the fictional revision, so this row scores 5.

  7. 7

    Dead dependency anchors

    A dependency, asset, generated client, or build step belongs here when dead or unwired code is its sole reason to remain. The archive SDK is retained only by the disconnected exporter, producing a score of 2.

  8. 8

    Ambiguous candidates

    Material uncertainty remains visible instead of being converted into a deletion claim. The registered Admin RPC service has one live operation but unclear ownership for the rest, and the dynamic analyzer was unavailable, so this row scores 2.

Reports

59 / 100July 17, 2026Current

Executive Summary

  • Disconnected residue materially slows understanding in one former product path, while one worker and one registered capability retain uncertainty.
  • Score: 59/100. Four evidence-backed reductions produce 64; confirmed residue across multiple surfaces caps the result at 59.
  • The complete fictional run has two confirmed, one likely unused, one ambiguous, one weak-signal, one intentionally dormant, and one false-positive candidate.

Audit Target

  • Repository: git.example.test/lighthouse/relay-console
  • Default branch: main
  • Audited commit: 9f6c2b8d1a4e7c3f5b0d6a2e8c1f4b7d9a3e5c6f
  • History depth: Full
  • Revision confidence: Full revision metadata; dynamic RPC analysis limited

What Was Checked

Application, router, worker, and public-package entry points; declared environment, script, and configuration surfaces; generated sources; documentation, examples, and tests; repository-native tool leads; and protected-use cases at the fictional revision.

Cleanup scorecard

CriterionScore (0-5)StatusWhat affected the score
Unwired code3⚠️The retry-delivery worker is likely unused, but one external invocation question remains.
Unused declarations5No meaningful candidate survived generated and public protected-use review.
Detached config, flags, parameters, or metadata2⚠️REPORT_CACHE_TTL is unread; archive config belongs to the broader confirmed root cause.
Commented or copied implementation5No meaningful disabled, backup, or copied cluster was found.
Obsolete feature residue1Archive Export residue crosses source, config, localization, and dependency surfaces.
Functionally irrelevant code5No meaningful executed-but-ineffective path was confirmed.
Dead dependency anchors2⚠️The archive SDK is retained only by the unwired archive exporter.
Ambiguous candidates2⚠️Admin RPC needs an owner decision, and dynamic RPC analysis was unavailable.
CopyDownload
This fictional current-report excerpt keeps all eight scorecard rows visible. Their 0-5 values explain category health; the separate 59/100 result comes from evidence-backed penalties and the multi-surface cap.

The complete report's Main answer uses a WARNING alert for this result. Findings then separate confirmed and likely cases, declared settings and commands, wired-but-underused capabilities, and ambiguous or protected cases. Each finding retains its class, confidence, priority, evidence, protected-use checks, missing context, and next action. This excerpt keeps those jobs out of one misleading table.

How penalties and caps produce the final score

The final score begins at 100. Guard classifies candidates first, then assigns an integer reduction supported by repository evidence. The allowed ranges are deliberately broad enough for impact and scope to matter without pretending that every unused symbol has a fixed price:

  • a confirmed broad finding across active surfaces removes 15–25 points;
  • a confirmed local finding removes 5–12;
  • a likely-unused case with one protected-use question removes 3–8;
  • a detached config, flag, parameter, or metadata declaration removes 5–15;
  • a commented or copied implementation cluster removes 2–8;
  • a dead dependency anchor removes 5–15;
  • material ambiguous reachability removes 3–10 per affected area;
  • a wired-but-underused capability removes zero when intentional or protected, or 3–8 only when partial use or unclear ownership materially impairs understanding;
  • a false positive or clearly intentional dormant path removes zero.

Each reduction records its amount, reason, category, and candidate IDs. There is no mechanical point picker inside a range and no duplicate charge for the same root cause. Archive Export leaves evidence in source, example configuration, localization, and an otherwise unnecessary SDK, but those surfaces support one broad DC-001reduction rather than four independent penalties.

raw_score = clamp(100 - sum(evidence_backed_penalties), 0, 100)
final_score = clamp(min(raw_score, every_applicable_cap), 0, 100)

Hard caps

Caps keep a serious interpretability limit from disappearing inside otherwise clean categories. Broad ambiguous residue or broad underused uncertainty in a major area caps the score at 69. Confirmed dead code crossing multiple repository surfaces caps it at 59. Systemic confirmed residue, or ambiguity so broad that the strongest candidate areas cannot be understood, caps it at 39. All applicable caps are considered and the most restrictive result wins.

A worked example: 59 out of 100

The fictional fixture has four reductions. DC-001 is confirmed broad Archive Export residue at −20. DC-002 is the confirmed detachedREPORT_CACHE_TTL setting at −7. DC-003 is a likely-unused worker at −5. DC-004 is a materially ambiguous underused capability at −4.

raw_score = 100 - 20 - 7 - 5 - 4 = 64
final_score = min(64, 59) = 59
Starting score

Before evidence-backed reductions

100
DC-001

Confirmed broad Archive Export residue

-20
DC-002

Confirmed detached setting

-7
DC-003

Likely-unused worker

-5
DC-004

Material underused-capability ambiguity

-4
Raw score

100 - 20 - 7 - 5 - 4

64
Final score after cap

Confirmed residue crosses multiple surfaces

59

The 69 ambiguity cap does not apply because Admin RPC uncertainty is bounded to one capability. The 39 cap does not apply because the confirmed residue is not systemic and the strongest areas remain understandable. The 59 cap does apply because the confirmed Archive Export root cause crosses source, configuration, localization, and dependency surfaces. Scores from 70 through 100 are green, 40 through 69 are warning, and values below 40 are bad, so 59 is a warning result.

A successful run with no reductions or caps may score 100 even when the repository has no dedicated cleanup tool. Missing tooling is a limitation, not a tariff. If identity, access, cloning, required inventory, or analysis fails, Guard publishes a diagnostic failure instead of a typed 0–100 result. A previous audit is never required to calculate the current score.

What can enter the cleanup queue

The audit is read-only, but it can prepare bounded context for a possible future cleanup. A candidate enters that queue only when its class is confirmed or likely unused, its priority is High, Medium, Low, or the exceptional Critical rather than Info, its type is not report-only underused capability, and its evidence, scope, next step, and blast radius are present. An empty queue is a valid outcome.

The fixture produces exactly three queued candidates in this order:

  1. DC-001: High, 94% confirmed Archive Export residue, with high blast radius;
  2. DC-002: Medium, 91% confirmed detached setting, with medium blast radius;
  3. DC-003: Medium, 78% likely-unused worker, with medium blast radius.

Higher priority comes first, followed by higher confidence and stable candidate ID.DC-004 is excluded because ambiguous underused capability evidence requires an owner decision. DC-005 is a weak signal, DC-006 is intentionally dormant, and DC-007 is a false positive. They stay visible in the report and cannot be converted into removal work merely to make the queue fuller.

Before any later mutation, an executor must revalidate:

  • the current repository state, full revision, and canonical target identity;
  • current entry-point reachability, consumers, and protected-use evidence;
  • missing context and the candidate's present classification;
  • existing provider issue and review-request state;
  • blast radius and the smallest safe bounded change.

A person must approve any change after that revalidation. Guard currently has no published improvement paired to the Dead code audit, so it exposes no Dead codeAutofix, no Ready to fix state, and noImprovement steps panel. The evidence handoff is a future-safe boundary, not a hidden write permission or a promise that a cleanup action exists today.

How current results and history appear in Guard

The repository dashboard compresses the current result into a score, score-derived badge, short narrative, and four generic tone buckets. For this fixture, the underlying summary has two confirmed-cleanup items in one rose row, plus amber rows for one likely-unused candidate, one manual check, and one verification limit. Guard therefore displaysPROBLEMS 1, WATCH 3, HEALTHY —, andSKIPPED —. These are counts of summary rows by tone, not candidate counts.

The dashboard exposes Audit details and Rerun. In the detail view, the action is labeled Rerun audit; its menu isRestart and automation and includes Run once now when a run is available. Completed reports appear under Reports. The newest disclosure carries Current, and its static document controls areCopy and Download. The score panel usesOverall score, while the history surface offersOpen larger chart.

Guard drafts the complete current summary and report before it reads any earlier result. If no valid previous locator exists, the current run becomes the baseline; the summary omits previous score and time, and the report briefly states the comparison limit. If only one prior file is readable, Guard compares only from that evidence and names the missing counterpart. A previous score and date appear in the current summary only when a target-matched previous summary was validated and read; the date comes from that prior audit context, not a guessed commit or report-render timestamp.

Movement needs a substantive explanation: repository changes, corrected evidence, a material scope or limitation change, relevant repository guidance, or a meaningful scoring-method change. Wording, unrelated commits, and cosmetic report churn do not justify movement. If the effective evidence is unchanged, an unexplained score change is assessment noise rather than a trend.

Dead code

Find disconnected code, configuration, flags, metadata, and dependency anchors that make current repository behavior harder to understand.

Runbook updated July 16, 2026Last run July 17, 2026
59/100
Overall scoreNeeds attention 12 vs last time
Audit score historyJul 02: 46; Jul 07: 64; Jul 12: 71; Jul 17: 590255075100Jul 02Jul 07Jul 12Jul 1746 points, Jul 0264 points, Jul 0771 points, Jul 1259 points, Jul 17
All dates, scores, revisions, and findings are fictional. The July 17 decline is attributed to an incomplete feature removal only because the target-matched previous summary was validated and read; report wording alone would not justify movement.

The sequence is 46 → 64 → 71 → 59. Older routes and a detached script were removed before July 7, and a dead generated-client anchor before July 12. The later Archive Export change left supporting surfaces behind. History records completed runs; it is not a forecast or a promise of monotonic improvement.

Limits, reruns, and maintained project health

A useful Dead code result makes its evidence boundary explicit:

  • Static reachability can miss reflection, dynamic dispatch, generated registration, framework conventions, external jobs, webhooks, CLIs, plugins, and public consumers.
  • Tests show isolated execution but do not establish a current product path by themselves.
  • Production telemetry, traffic, route analytics, and feature-flag data are not default targets; safely supplied runtime evidence can support a conclusion.
  • In the fictional run, an unavailable dynamic RPC analyzer leaves Admin RPC ambiguous. The audit preserves that uncertainty instead of guessing.
  • Every finding and score describes the audited full commit. A later route, export, generated-client, flag, or configuration change can alter reachability.
  • Missing optional tooling or repository guidance is neutral unless the missing evidence creates a concrete material ambiguity under the scoring rules.

After a successful run, Guard does not offer a manual rerun while the repository remains on the same already-audited HEAD. The audit becomes rerunnable after the repository changes, so the next result can be tied to a new exact revision. That constraint prevents repeated identical runs from looking like new evidence.

Green is a maintained state

A clean result is not permanent. Feature removal can leave configuration and localization behind; a rollback can revive an old path; a generated client can expose new protected entry points. Recurring audits detect those changes and show when the repository has moved out of the green zone. Any later cleanup still requires bounded evidence, revalidation, and a human decision.

That maintained state matters because coding agents inherit the codebase they are asked to extend. When live behavior is easier to distinguish from obsolete and uncertain residue, agents spend less effort reconstructing which paths are real and are less likely to layer new features on top of misleading surfaces.