Vulture and deadcode tools for detecting unused Python code (functions, classes, variables, imports). Use when cleaning up codebases, removing unused code, or enforcing code hygiene in CI. Triggered by: vulture, deadcode, dead code detection, unused code, code cleanup, remove unused.
/plugin marketplace add laurigates/claude-plugins/plugin install python-plugin@lgates-claude-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Tools for finding unused Python code including functions, classes, variables, imports, and attributes.
Vulture (mature, confidence-based) and deadcode (newer, AST-based) both detect unused code but with different approaches:
| Feature | Vulture | deadcode |
|---|---|---|
| Approach | Static analysis + confidence scores | AST-based detection |
| Accuracy | Confidence scores (60-100%) | High accuracy, fewer false positives |
| Speed | Fast | Very fast |
| Configuration | Whitelist files | TOML configuration |
| Maturity | Mature (2012) | Newer (2023+) |
| Best For | Large codebases, gradual cleanup | New projects, strict enforcement |
# Install vulture
uv add --dev vulture
# Install deadcode (newer alternative)
uv add --dev deadcode
# Install both for comparison
uv add --dev vulture deadcode
# Check entire project
vulture .
# Check specific files/directories
vulture src/ tests/
# Minimum confidence threshold (60-100%)
vulture --min-confidence 80 .
# Exclude patterns
vulture . --exclude "**/migrations/*,**/tests/*"
# Sort by confidence
vulture --sort-by-size .
# Generate whitelist of current issues
vulture . --make-whitelist > vulture_whitelist.py
[tool.vulture]
# Minimum confidence to report (60-100%)
min_confidence = 80
# Paths to scan
paths = ["src", "tests"]
# Exclude patterns (glob)
exclude = [
"**/migrations/*",
"**/__pycache__/*",
"**/node_modules/*",
".venv/*"
]
# Ignore decorators (marks functions as used)
ignore_decorators = [
"@app.route",
"@pytest.fixture",
"@property",
"@staticmethod",
"@classmethod"
]
# Ignore names matching patterns
ignore_names = [
"test_*", # Test functions
"setUp*", # Test setup
"tearDown*", # Test teardown
]
# Make whitelist
make_whitelist = false
# Sort results
sort_by_size = false
# vulture_whitelist.py
# Whitelist for false positives
# Used by external code
_.used_by_external_lib # confidence: 60%
MyClass.called_dynamically # confidence: 60%
# Used in templates
def render_template_helper():
pass # confidence: 60%
# Used via __getattr__
dynamic_attribute = None # confidence: 60%
# Framework magic
class Meta: # Django/Flask metadata
pass
# Plugin system
def plugin_hook(): # Called by plugin system
pass
# 100% confidence (definitely unused)
def never_called():
"""This function is never called anywhere."""
pass
# 80% confidence (likely unused)
def maybe_called():
"""Called in commented code or string."""
pass
# 60% confidence (possibly unused)
def dynamic_call():
"""Might be called via getattr() or string."""
pass
# Set minimum confidence threshold
# --min-confidence 80 = Report only high-confidence issues
# --min-confidence 60 = Report all potential issues (more false positives)
# FOUND: Unused import
import sys # confidence: 100%
import os # confidence: 100%
# USED: Import is used
import logging
logger = logging.getLogger(__name__)
# FOUND: Unused function
def unused_helper(): # confidence: 100%
return 42
# USED: Function is called
def used_helper():
return 42
result = used_helper()
class MyClass:
# FOUND: Unused attribute
unused_attr = 42 # confidence: 100%
# USED: Attribute is accessed
used_attr = 42
def method(self):
return self.used_attr
def process():
# FOUND: Unused variable
unused = calculate() # confidence: 100%
# USED: Variable is used
result = calculate()
return result
# 1. Dynamic attribute access
class Config:
DEBUG = True # Accessed via getattr(config, 'DEBUG')
# 2. Framework magic
class Meta: # Used by Django ORM
db_table = 'users'
# 3. Decorators
@app.route('/api/data')
def api_endpoint(): # Route handler - appears unused
pass
# 4. Test fixtures
@pytest.fixture
def sample_data(): # Fixture - appears unused
return [1, 2, 3]
# 5. Plugin hooks
def plugin_initialize(): # Called by plugin system
pass
# 6. Serialization
class User:
def to_dict(self): # Called by serialization library
pass
# Step 1: Generate baseline
vulture --make-whitelist > vulture_whitelist.py
# Step 2: Fix high-confidence issues
vulture --min-confidence 90 .
# Step 3: Lower threshold gradually
vulture --min-confidence 80 .
vulture --min-confidence 70 .
# Step 4: Update whitelist
vulture --make-whitelist > vulture_whitelist.py
# Step 5: Enforce in CI
vulture --min-confidence 80 .
# Check entire project
deadcode .
# Check specific files/directories
deadcode src/
# Verbose output
deadcode --verbose .
# Dry run (show what would be removed)
deadcode --dry-run .
# Show unreachable code
deadcode --show-unreachable .
# Generate configuration
deadcode --init
[tool.deadcode]
# Paths to scan
paths = ["src"]
# Exclude patterns
exclude = [
"tests/*",
"**/__pycache__/*",
"**/migrations/*",
]
# Files to ignore completely
ignore_files = [
"src/legacy.py",
"src/experimental.py"
]
# Directories to exclude
exclude_dirs = [
".venv",
"node_modules",
".git"
]
# Functions/classes to ignore
ignore_names = [
"test_*", # Test functions
"setUp", # Test methods
"tearDown",
"main", # Entry points
]
# Ignore decorators
ignore_decorators = [
"app.route",
"pytest.fixture",
"property",
"staticmethod",
"classmethod",
"abstractmethod"
]
# Minimum number of references to consider "used"
min_references = 1
# Show unreachable code (after return/raise)
show_unreachable = false
# FOUND: Unused function
def unused_function():
return 42
# FOUND: Unused class
class UnusedClass:
pass
# FOUND: Unused variable
UNUSED_CONSTANT = 42
# FOUND: Unused import
import unused_module
def example():
return 42
print("Unreachable") # FOUND: Code after return
def example2():
raise ValueError("Error")
cleanup() # FOUND: Code after raise
def example3():
if True:
return
else:
process() # FOUND: Unreachable branch
# 1. Public API (keep even if unused internally)
# Add to ignore_names in pyproject.toml
[tool.deadcode]
ignore_names = ["PublicAPIClass", "public_function"]
# 2. Framework magic
[tool.deadcode]
ignore_decorators = ["app.route", "celery.task"]
# 3. Test infrastructure
[tool.deadcode]
ignore_names = ["test_*", "setUp*", "tearDown*"]
# 4. Entry points
[tool.deadcode]
ignore_names = ["main", "__main__"]
Use Vulture for:
Example workflow:
# Start with high confidence
vulture --min-confidence 90 .
# Generate whitelist for false positives
vulture --make-whitelist > vulture_whitelist.py
# Edit whitelist manually
vim vulture_whitelist.py
# Run with whitelist
vulture . vulture_whitelist.py
Use deadcode for:
Example workflow:
# Generate initial config
deadcode --init
# Configure in pyproject.toml
[tool.deadcode]
paths = ["src"]
ignore_decorators = ["app.route"]
# Run checks
deadcode .
# Enforce in CI
deadcode --strict .
Use both tools for comprehensive detection:
# Run vulture for broad detection
vulture --min-confidence 80 .
# Run deadcode for precise detection
deadcode .
# Compare results and whitelist false positives
# .github/workflows/deadcode.yml
name: Dead Code Check
on: [push, pull_request]
jobs:
vulture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v2
- name: Set up Python
run: uv python install 3.12
- name: Install dependencies
run: uv sync --all-extras --dev
- name: Run vulture
run: |
uv run vulture . \
--min-confidence 80 \
vulture_whitelist.py
- name: Upload results
uses: actions/upload-artifact@v4
if: failure()
with:
name: vulture-results
path: vulture-output.txt
# .github/workflows/deadcode.yml
name: Dead Code Check
on: [push, pull_request]
jobs:
deadcode:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v2
- name: Set up Python
run: uv python install 3.12
- name: Install dependencies
run: uv sync --all-extras --dev
- name: Run deadcode
run: uv run deadcode .
- name: Check for unreachable code
run: uv run deadcode --show-unreachable .
# .pre-commit-config.yaml
repos:
# Vulture
- repo: https://github.com/jendrikseipp/vulture
rev: v2.11
hooks:
- id: vulture
args: ['--min-confidence', '80']
files: ^src/
# deadcode
- repo: https://github.com/albertas/deadcode
rev: v2.0.0
hooks:
- id: deadcode
args: ['.']
files: ^src/
# Begin with high confidence (fewer false positives)
vulture --min-confidence 90 .
# Gradually lower threshold
vulture --min-confidence 80 .
vulture --min-confidence 70 .
# vulture_whitelist.py
# Document WHY each item is whitelisted
# Framework routes (Flask/Django)
@app.route('/api/endpoint')
def api_handler(): # Called by framework
pass
# Pytest fixtures
@pytest.fixture
def sample_data(): # Used by test functions
return [1, 2, 3]
# Plugin hooks
def on_load(): # Called by plugin system
pass
# Local development: Quick check
vulture src/ --min-confidence 90
# Pre-commit: Catch obvious issues
pre-commit run vulture --all-files
# CI: Strict enforcement
vulture . --min-confidence 80 vulture_whitelist.py
# Weekly: Review dead code
vulture --make-whitelist > current_issues.txt
# Compare with last week
diff current_issues.txt last_week_issues.txt
# Clean up incrementally
git grep "def unused_function" | xargs -I {} git rm {}
# Dead code + unused imports
vulture . && ruff check --select F401 .
# Dead code + type checking
vulture . && basedpyright
# Dead code + coverage
pytest --cov=src && vulture src/
# Problem: vulture can't detect dynamic usage
def dynamic_call():
pass
# Called via getattr
func = getattr(module, "dynamic_call")
func()
# Solution: Whitelist or use ignore comment
def dynamic_call(): # noqa: vulture
pass
# Problem: Test fixtures appear unused
@pytest.fixture
def sample_data(): # Appears unused to vulture
return [1, 2, 3]
# Solution: Configure ignore_decorators
[tool.vulture]
ignore_decorators = ["pytest.fixture"]
# Problem: Public API unused internally
class PublicAPI:
def public_method(self): # Not called in codebase
pass
# Solution: Whitelist or document
[tool.deadcode]
ignore_names = ["PublicAPI"]
# Problem: Framework calls code dynamically
class Meta: # Django ORM metadata
db_table = 'users'
@app.route('/api') # Flask route
def endpoint():
pass
# Solution: Configure ignore_decorators
[tool.vulture]
ignore_decorators = ["app.route"]
ignore_names = ["Meta"]
// settings.json
{
"python.linting.enabled": true,
"python.linting.vulture.enabled": true,
"python.linting.vulture.args": [
"--min-confidence", "80"
]
}
-- Using null-ls
local null_ls = require("null-ls")
null_ls.setup({
sources = {
null_ls.builtins.diagnostics.vulture.with({
extra_args = { "--min-confidence", "80" }
}),
}
})
Vulture provides confidence-based dead code detection:
uv add --dev vulturevulture --min-confidence 80 .vulture_whitelist.py for false positivesdeadcode provides AST-based detection:
uv add --dev deadcodedeadcode .pyproject.toml with [tool.deadcode]Best practices:
Hybrid approach:
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.