A reusable GitHub Actions workflow for detecting and removing dead code across multilingual repositories.
| Language | Tool | Auto-Fix | Notes |
|---|---|---|---|
| Rust | cargo-machete | ✅ | Fast heuristic-based detection |
| Python | deadcode | ✅ | Scope-aware, catches more than vulture |
| TypeScript/JS | Knip | ✅ | Monorepo-native, excellent accuracy |
Plus stale file detection using git history.
- Create
.github/workflows/cleanup.ymlin your repository:
name: Dead Code Cleanup
on:
schedule:
- cron: "15 3 * * 1" # Weekly Monday 3:15 AM UTC
workflow_dispatch:
jobs:
cleanup:
uses: victorem/cleanup-bot/.github/workflows/cleanup.yml@main
with:
rust_paths: "." # Path to Cargo.toml, or "" to skip
python_paths: "./src" # Comma-separated paths, or "" to skip
typescript_paths: "" # Path to package.json, or "" to skip
secrets:
token: ${{ secrets.GITHUB_TOKEN }}- The workflow will:
- Run weekly (or on manual trigger)
- Detect dead code in configured languages
- Auto-fix where possible
- Create a PR for review
| Input | Type | Default | Description |
|---|---|---|---|
rust_paths |
string | "" |
Path to Rust workspace root. Empty to skip. |
python_paths |
string | "" |
Comma-separated Python paths. Empty to skip. |
typescript_paths |
string | "" |
Path to TS/JS project root. Empty to skip. |
stale_days |
number | 365 |
Days without commits = stale |
stale_patterns |
string | *.py,*.rs,*.ts,... |
Patterns for stale detection |
auto_fix |
boolean | true |
Apply auto-fixes |
create_pr |
boolean | true |
Create PR (false = report only) |
exclude_paths |
string | target,node_modules,... |
Paths to exclude |
pr_branch |
string | chore/dead-code-cleanup |
PR branch name |
pr_labels |
string | maintenance,automated |
PR labels |
| Secret | Required | Description |
|---|---|---|
token |
Yes | GitHub token for creating PRs |
jobs:
cleanup:
uses: victorem/cleanup-bot/.github/workflows/cleanup.yml@main
with:
rust_paths: "."
secrets:
token: ${{ secrets.GITHUB_TOKEN }}jobs:
cleanup:
uses: victorem/cleanup-bot/.github/workflows/cleanup.yml@main
with:
python_paths: "./backend,./scripts,./tests"
exclude_paths: ".venv,__pycache__,migrations"
secrets:
token: ${{ secrets.GITHUB_TOKEN }}jobs:
cleanup:
uses: victorem/cleanup-bot/.github/workflows/cleanup.yml@main
with:
rust_paths: "./backend"
python_paths: "./scripts,./ml"
typescript_paths: "./frontend"
stale_days: 180
secrets:
token: ${{ secrets.GITHUB_TOKEN }}jobs:
cleanup:
uses: victorem/cleanup-bot/.github/workflows/cleanup.yml@main
with:
rust_paths: "."
python_paths: "./python"
auto_fix: false
create_pr: false # Just generates artifact report
secrets:
token: ${{ secrets.GITHUB_TOKEN }}- Unused dependencies in
Cargo.toml - Works across workspace members
- Unused functions, classes, variables
- Unused imports (better than ruff/flake8 for cross-file)
- Unreachable code
- Unused exports
- Unused dependencies
- Unused files
- Unused types
- Files not modified in git for N days
- Helps identify abandoned code/data
Add to Cargo.toml:
[workspace.metadata.cargo-machete]
ignored = ["some-crate", "another-crate"]Add to pyproject.toml:
[tool.deadcode]
exclude = ["tests", "migrations"]
ignore-names = ["*Mixin", "Base*"]Add to knip.json:
{
"ignore": ["src/legacy/**"],
"ignoreDependencies": ["some-dev-tool"]
}The scripts/ directory contains standalone tools:
# Find files not modified in 365 days
python scripts/find_stale_files.py 365
# Custom patterns and exclusions
python scripts/find_stale_files.py 180 \
--patterns "*.py,*.json" \
--exclude ".venv,__pycache__"
# JSON output for automation
python scripts/find_stale_files.py 365 --jsonFor better security and audit trails, use a GitHub App instead of GITHUB_TOKEN:
- Create a GitHub App with
contents: writeandpull_requests: writepermissions - Install on your repository
- Use actions/create-github-app-token:
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ vars.CLEANUP_BOT_APP_ID }}
private-key: ${{ secrets.CLEANUP_BOT_PRIVATE_KEY }}
- uses: victorem/cleanup-bot/.github/workflows/cleanup.yml@main
with:
rust_paths: "."
secrets:
token: ${{ steps.app-token.outputs.token }}┌─────────────────────────────────────────────────────────────┐
│ Scheduled Trigger (cron) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 1. Checkout with full git history │
│ 2. For each configured language: │
│ - Install tool (cargo-machete / deadcode / knip) │
│ - Run detection │
│ - Apply fixes (if auto_fix=true) │
│ 3. Detect stale files via git log │
│ 4. Generate combined report │
│ 5. Create PR (if changes exist) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ PR: "Dead Code Cleanup" │
│ - Shows what was detected │
│ - Shows what was auto-fixed │
│ - Lists stale files for manual review │
└─────────────────────────────────────────────────────────────┘
Issues and PRs welcome. This is a personal project but happy to accept improvements.
MIT