BXUP Track progress & test yourself — free
Software Engineering · Playbook · Core · ~5 min read
SOFTWARE ENGINEERING · PLAYBOOK · CORE

Dead Code and Dependency Sweep: No Deletion Without an Evidence Trail

Dead code is a tax on every future reader, every migration, and every agent context window — but deleting the wrong "dead" code is a production incident.

Tier: Core · Workflow area: Legacy Modernization

The discipline that makes sweeps safe: static analyzers and usage data nominate candidates, the AI assembles an evidence dossier per deletion, and nothing is removed without cited proof plus a grace period. The rule is absolute — no delete without evidence — because "the model was pretty sure" is not a rollback plan.

When to use

  • Before a migration or upgrade (pb-21/pb-23): every dead file you delete first is migration work you skip.
  • Dependency bloat: package.json/requirements.txt full of libraries nobody remembers adding — each one attack surface and upgrade drag.
  • Periodic hygiene on a long-lived service, or after a feature deprecation that "removed" a feature but not its code.

Prerequisites

  • Static detectors for your stack: vulture (Python), knip (JS/TS — files, exports, and deps), depcheck, deptry, ts-prune, or compiler dead-code warnings.
  • Runtime truth where possible: production coverage/tracing (even a week of it), access logs for endpoints, feature-flag states.
  • Grep across all repos and config that might reference code dynamically — templates, CI, cron, infra, other services.

The workflow

  1. Generate candidates deterministically.

    vulture src/ --min-confidence 80 > /tmp/dead-candidates.txt
    knip --reporter json > /tmp/knip.json          # unused files/exports/deps
    deptry . > /tmp/deps-report.txt                # unused + missing deps
    

    Static tools over-report by design (dynamic dispatch, reflection, plugins, template references are invisible to them). Their output is a worklist, never a delete list.

  2. Build an evidence dossier per candidate. This is where the AI earns its place — orchestrating the checks and writing up results, one candidate at a time:

    Candidate: src/billing/legacy_tax.py::compute_gst_v1 (vulture, conf 90).
    Build the evidence dossier by actually checking, citing each result:
    1. STATIC: grep for the symbol across the repo — direct refs, string refs
       ("compute_gst_v1" as string: getattr, celery task names, URL confs,
       templates, YAML/JSON configs, alembic/migrations).
    2. RUNTIME: is it in the attached production coverage report? Any hits in
       the attached endpoint access summary?
    3. HISTORY: `git log --oneline -5 --follow` for the file — recently
       touched? Referenced in any open PR/branch?
    4. EXTERNAL: could anything outside this repo call it? (public API route,
       exported package symbol, scheduled job defined in infra repo?)
    VERDICT: DELETE (all checks negative, cite each) / KEEP (cite the hit) /
    UNCERTAIN (name exactly which check couldn't be completed).
    

    Anything the model cannot actually check must come back UNCERTAIN — an agent that can run grep and read the coverage file fills most dossiers itself; a chat model gets the outputs pasted in. UNCERTAIN is a fine verdict; a guessed DELETE is the only wrong output.

  3. Handle dependencies with extra paranoia. For each unused-dependency candidate: grep for imports and side-effect usage (registered plugins, pytest plugins, polyfills, Django apps, webpack loaders — packages that do things when merely installed), check lockfile reverse-deps (npm ls <pkg>, pipdeptree -r -p <pkg>) so you don't remove something another dep needs peer-installed, and check config files that name packages as strings (.eslintrc, pytest.ini, babel.config). Then remove one per commit, full CI + build between each.

  4. Delete with a tombstone process, not a hard drop. For DELETE verdicts:

    • Deprecation window where risk warrants it: log-on-call first (logger.warning("DEPRECATED compute_gst_v1 called")), deploy, watch the logs for a cycle (a week of silence beats a month of speculation), then delete.
    • One logical unit per commit, dossier summary in the commit message: Remove compute_gst_v1: no static refs (grep 2026-07-24), 0 prod calls in 30d coverage, last real change 2019 (a1b2c3d).
    • The commit message is the evidence trail — future archaeologists (and reverting engineers) get the full case without asking.
  5. Verify, ratchet, and re-run on cadence. After the sweep: full CI, build artifacts, smoke tests in staging, and — for endpoint deletions — a 404-rate check post-deploy. Then keep the floor from rising again: wire knip/vulture/deptry into CI as a ratchet (count may only decrease), and put the sweep on a quarterly cadence with the saved prompts. First-sweep etiquette: start with the lowest-risk tier (unused imports, unreferenced test fixtures, commented-out blocks) to calibrate your process before touching "unused" business logic.

Warning

Dynamic dispatch is where dead-code sweeps kill production — code invoked via getattr, string-named celery tasks, URL confs, template tags, and reflection shows zero static references while running daily. The string-reference grep in the dossier is not optional, and UNCERTAIN verdicts stay undeleted.

Tool variants

  • Claude Code / agent in-repo: runs the whole dossier loop itself (grep, git log, reading coverage reports) and drafts tombstone commits; restrict tools to read + grep during dossier phase so investigating can't quietly become deleting.
  • Copilot Chat / any chat model: paste detector output + your grep/coverage results per candidate; the model structures dossiers and catches checks you skipped — slower, works on the free tier.
  • knip standalone (JS/TS): closest to one-tool coverage (files + exports + deps) with CI mode for the ratchet; pair its output with the dossier discipline for anything non-obvious.

Verification

  • Every deletion commit contains its dossier summary; sampling 5 commits, each check is real (the grep was actually run, the coverage file actually covered the window claimed).
  • CI, build, and staging smoke green after each removal commit, not just at branch end.
  • Deprecation-log watch completed for the flagged-risk deletions: zero calls over the stated window.
  • Post-deploy: no new 404s/error-rate change attributable to the sweep; the CI ratchet is live and the candidate count went down.

Pitfalls

  • Bulk deletion from detector output. vulture/depcheck false-positive on dynamic patterns constantly; one incident erases a year of hygiene goodwill. Dossier per candidate, no exceptions.
  • Trusting the model's "this is unreferenced". LLMs answer reachability questions confidently without running anything. Evidence means executed greps and read reports — verify the model actually ran them (agents) or ran them yourself (chat).
  • Deleting side-effect packages. The classic: a package imported nowhere that registers a pytest plugin or polyfill at install time; the suite breaks two sweeps later. Reverse-dep and config-string checks catch these.
  • Test-coverage theater. "Covered by tests" only proves tests call it; production coverage or access data is the runtime check that counts.
  • The mega-sweep PR. Forty deletions in one PR is unreviewable and unbisectable; the whole design (one commit per unit, dossier in message) exists so git bisect and git revert stay surgical.

Restricted-environment variant

  • The detectors, greps, git history, and coverage analysis are all local — this playbook runs end-to-end with zero code leaving the machine; the LLM's role (orchestration + dossier write-up) is the most easily sanitized or dropped part.
  • Under client policy, use the sanctioned assistant for dossier structuring over detector outputs and grep results (symbol names and counts) rather than source — usually acceptable, confirm first; a local Ollama model handles dossier drafting fine since the reasoning is checklist-shaped.
  • Production coverage/access data may itself be restricted (client telemetry): substitute a staging soak with production-shaped traffic, and lengthen the log-on-call window accordingly.

Success criteria

  • Measurable shrinkage with zero sweep-caused incidents: fewer files, exports, and dependencies, each removal individually revertible with its evidence attached.
  • UNCERTAIN candidates are documented and parked, not deleted — and several convert to DELETE next quarter as evidence accumulates.
  • The ratchet holds: detector counts trend down across quarters, and new dead code gets caught in CI rather than accumulating for the next heroic sweep.
This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in