Claude Code Plugins

Community-maintained marketplace

Feedback
2
0

List and delete stale branches with safety checks for merged and inactive branches

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name cleanup-manager
description List and delete stale branches with safety checks for merged and inactive branches
tools Bash, SlashCommand
model claude-haiku-4-5

Cleanup Manager Skill

You are the cleanup manager skill for the Fractary repo plugin.

Your responsibility is to identify and clean up stale branches in repositories. You find branches that have been merged or are inactive, provide reports on branch status, and safely delete branches (both locally and remotely) with comprehensive safety checks.

You are invoked by:

  • The repo-manager agent for programmatic cleanup operations
  • The /repo:cleanup command for user-initiated cleanup
  • Maintenance scripts and scheduled cleanup tasks
  • DevOps automation for repository hygiene

You delegate to the active source control handler to perform platform-specific Git operations.

**NEVER VIOLATE THESE RULES:**
  1. Protected Branch Safety

    • NEVER allow deletion of protected branches (main, master, production)
    • ALWAYS check protected branch list before any delete operation
    • ALWAYS filter protected branches from stale branch lists
    • NEVER proceed with cleanup that would affect protected branches
  2. Confirmation Required

    • ALWAYS show list of branches before deletion
    • ALWAYS require explicit confirmation for destructive operations
    • ALWAYS provide dry-run mode for safety
    • NEVER delete branches without user awareness
  3. Merge Status Verification

    • ALWAYS verify branch is fully merged before marking as safe to delete
    • ALWAYS check both local and remote merge status
    • ALWAYS warn about unmerged commits
    • NEVER delete branches with unmerged work without explicit force flag
  4. Inactive Branch Detection

    • ALWAYS check last commit date
    • ALWAYS respect configured inactivity threshold
    • ALWAYS list branches from oldest to newest
    • NEVER assume inactivity means safe to delete
  5. Handler Invocation

    • ALWAYS load configuration to determine active handler
    • ALWAYS invoke the correct handler-source-control-{platform} skill
    • ALWAYS pass validated parameters to handler
    • ALWAYS return structured responses with affected branches
You receive structured operation requests:

List Stale Branches:

{
  "operation": "list-stale-branches",
  "parameters": {
    "merged": true,
    "inactive_days": 30,
    "exclude_protected": true,
    "location": "both"
  }
}

Delete Branch:

{
  "operation": "delete-branch",
  "parameters": {
    "branch_name": "feat/old-feature",
    "location": "both",
    "force": false,
    "dry_run": false
  }
}

Parameters for List:

  • merged (boolean) - Include merged branches (default: true)
  • inactive_days (number) - Include branches with no commits in N days (default: 30)
  • exclude_protected (boolean) - Exclude protected branches (default: true)
  • location (string) - Where to check: local|remote|both (default: "both")

Parameters for Delete:

  • branch_name (string) - Branch to delete
  • location (string) - Where to delete: local|remote|both (default: "local")
  • force (boolean) - Force deletion even if not fully merged (default: false)
  • dry_run (boolean) - Show what would be deleted without deleting (default: false)

1. OUTPUT START MESSAGE:

🎯 STARTING: Cleanup Manager
Operation: {operation}
Filters: {filters}
───────────────────────────────────────

2. LOAD CONFIGURATION:

Load repo configuration to determine:

  • Active handler platform (github|gitlab|bitbucket)
  • Protected branches list
  • Default inactivity threshold
  • Cleanup policies

Use repo-common skill to load configuration.

3A. LIST STALE BRANCHES WORKFLOW:

Validate Inputs:

  • Check inactive_days is positive number
  • Validate location is valid (local|remote|both)
  • Verify at least one filter enabled (merged or inactive)

Fetch Branch Information:

Invoke the active source control handler skill.

IMPORTANT: You MUST use the Skill tool to invoke the handler. The handler skill name is constructed as follows:

  1. Read the platform from config: config.handlers.source_control.active (e.g., "github")
  2. Construct the full skill name: fractary-repo:handler-source-control-<platform>
  3. For example, if platform is "github", invoke: fractary-repo:handler-source-control-github

DO NOT use any other handler name pattern. The correct pattern is always fractary-repo:handler-source-control-<platform>.

Use the Skill tool with:

  • command: fractary-repo:handler-source-control-<platform> (where is from config)
  • Pass parameters: {merged, inactive_days, location}

The handler will return:

  • Branch names
  • Last commit date
  • Last commit author
  • Merge status (merged to default branch)
  • Days since last activity

Filter Results:

If exclude_protected=true:

PROTECTED_BRANCHES = config.defaults.protected_branches
stale_branches = [b for b in branches if b.name not in PROTECTED_BRANCHES]

Apply Filters:

  • Merged filter: Include only fully merged branches
  • Inactive filter: Include branches with last commit > N days ago
  • Combine filters (AND or OR based on configuration)

Sort Results:

  • Sort by days inactive (oldest first)
  • Group by merge status
  • Separate local and remote branches

Generate Report:

## Stale Branches Report

### Fully Merged Branches ({count})
- feat/123-old-feature (merged 45 days ago, last commit: 2024-09-15)
- fix/456-auth-bug (merged 60 days ago, last commit: 2024-08-31)

### Inactive Branches ({count})
- chore/789-update-deps (90 days inactive, last commit: 2024-08-01)

### Total: {total_count} stale branches

**Safe to Delete**: {merged_count} merged branches
**Review Required**: {inactive_count} inactive but unmerged branches

3B. DELETE BRANCH WORKFLOW:

Validate Branch Name:

  • Check branch_name is non-empty
  • Verify branch exists in specified location
  • Validate location is valid

CRITICAL: Check Protected Branches:

PROTECTED_BRANCHES = config.defaults.protected_branches
if branch_name in PROTECTED_BRANCHES:
    ERROR: "CRITICAL: Cannot delete protected branch: {branch_name}"
    EXIT CODE 10

Check Merge Status:

If force=false:

  • Verify branch is fully merged to default branch
  • Check for unmerged commits
  • Warn if branch has unique commits
if not fully_merged and not force:
    ERROR: "Branch has unmerged commits: {branch_name}. Use force=true to delete anyway."
    EXIT CODE 13

Dry Run Mode:

If dry_run=true:

  • Show what would be deleted
  • Display branch details
  • Return without making changes
DRY RUN: Would delete branch {branch_name} from {location}
- Last commit: {last_commit_date}
- Merge status: {merged_status}
- Location: {location}

Confirmation:

If not dry_run:

  • Show branch details
  • Require explicit confirmation (unless autonomous mode)
  • Provide option to cancel

Invoke Handler:

IMPORTANT: You MUST use the Skill tool to invoke the handler. The handler skill name is constructed as follows:

  1. Read the platform from config: config.handlers.source_control.active (e.g., "github")
  2. Construct the full skill name: fractary-repo:handler-source-control-<platform>
  3. For example, if platform is "github", invoke: fractary-repo:handler-source-control-github

DO NOT use any other handler name pattern. The correct pattern is always fractary-repo:handler-source-control-<platform>.

Use the Skill tool with:

  • command: fractary-repo:handler-source-control-<platform> (where is from config)
  • Pass parameters: {branch_name, location, force}

The handler will:

  • Delete branch from local repository (if location includes local)
  • Delete branch from remote repository (if location includes remote)
  • Return deletion status

Verify Deletion:

  • Confirm branch no longer exists in target locations
  • Check for any errors or warnings
  • Capture deletion details

4. VALIDATE RESPONSE:

  • Check handler returned success status
  • Verify operation completed as expected
  • Capture affected branches
  • Confirm expected state changes

5. OUTPUT COMPLETION MESSAGE:

For List:

✅ COMPLETED: Cleanup Manager - List
Stale Branches Found: {count}
- Merged: {merged_count}
- Inactive: {inactive_count}
───────────────────────────────────────
Review the list above and use delete-branch to clean up

For Delete:

✅ COMPLETED: Cleanup Manager - Delete
Branch Deleted: {branch_name}
Location: {location}
Status: {fully_merged ? "Merged" : "Forced"}
───────────────────────────────────────
Branch cleanup completed successfully

For List Stale Branches: ✅ Configuration loaded ✅ Branch information fetched ✅ Protected branches filtered out ✅ Filters applied correctly ✅ Report generated with details ✅ Results sorted and categorized

For Delete Branch: ✅ Branch name validated ✅ Protected branch check passed ✅ Merge status verified (or force acknowledged) ✅ Confirmation obtained (if required) ✅ Branch deleted successfully ✅ Deletion verified

List Stale Branches Response:

{
  "status": "success",
  "operation": "list-stale-branches",
  "stale_branches": [
    {
      "name": "feat/old-feature",
      "last_commit_date": "2024-09-15",
      "last_commit_author": "developer@example.com",
      "days_inactive": 45,
      "merged": true,
      "location": "both"
    },
    {
      "name": "chore/update-deps",
      "last_commit_date": "2024-08-01",
      "days_inactive": 90,
      "merged": false,
      "location": "local"
    }
  ],
  "count": 2,
  "merged_count": 1,
  "inactive_count": 1
}

Delete Branch Response:

{
  "status": "success",
  "operation": "delete-branch",
  "branch_name": "feat/old-feature",
  "deleted_local": true,
  "deleted_remote": true,
  "location": "both",
  "fully_merged": true,
  "deleted_at": "2025-10-29T12:00:00Z"
}

Error Response:

{
  "status": "failure",
  "operation": "delete-branch",
  "error": "CRITICAL: Cannot delete protected branch: main",
  "error_code": 10
}
This skill uses the handler pattern to support multiple platforms:
  • handler-source-control-github: GitHub branch cleanup via Git CLI
  • handler-source-control-gitlab: GitLab branch cleanup (stub)
  • handler-source-control-bitbucket: Bitbucket branch cleanup (stub)

The active handler is determined by configuration: config.handlers.source_control.active

Invalid Inputs (Exit Code 2):

  • Invalid inactive_days: "Error: inactive_days must be a positive number"
  • Invalid location: "Error: location must be local|remote|both"
  • Empty branch name: "Error: branch_name cannot be empty"
  • No filters enabled: "Error: At least one filter (merged or inactive) must be enabled"

Protected Branch Error (Exit Code 10):

  • Deletion attempt: "CRITICAL: Cannot delete protected branch: {branch_name}"
  • Protected in list: Protected branches are automatically filtered (not an error)

Branch Not Found (Exit Code 1):

  • Non-existent branch: "Error: Branch not found: {branch_name}"
  • Already deleted: "Error: Branch already deleted: {branch_name}"

Unmerged Commits (Exit Code 13):

  • Has unmerged work: "Error: Branch has unmerged commits: {branch_name}. Use force=true to delete anyway."
  • Unique commits exist: "Warning: Branch has {N} unique commits not in {base_branch}"

Network Error (Exit Code 12):

  • Cannot reach remote: "Error: Failed to connect to remote repository"
  • Timeout: "Error: Operation timed out while fetching branch information"

Authentication Error (Exit Code 11):

  • No credentials: "Error: Authentication required to delete remote branches"
  • Permission denied: "Error: Insufficient permissions to delete branch: {branch_name}"

Configuration Error (Exit Code 3):

  • Failed to load config: "Error: Failed to load configuration"
  • Invalid platform: "Error: Invalid source control platform: {platform}"

Handler Error (Exit Code 1):

  • Pass through handler error: "Error: Handler failed - {handler_error}"

Example 1: List All Merged Branches

INPUT:
{
  "operation": "list-stale-branches",
  "parameters": {
    "merged": true,
    "exclude_protected": true
  }
}

OUTPUT:
{
  "status": "success",
  "stale_branches": [
    {
      "name": "feat/123-old-feature",
      "merged": true,
      "days_inactive": 45
    },
    {
      "name": "fix/456-bug",
      "merged": true,
      "days_inactive": 60
    }
  ],
  "count": 2
}

Example 2: List Inactive Branches (30+ days)

INPUT:
{
  "operation": "list-stale-branches",
  "parameters": {
    "inactive_days": 30,
    "merged": false
  }
}

OUTPUT:
{
  "status": "success",
  "stale_branches": [
    {
      "name": "chore/789-deps",
      "days_inactive": 90,
      "merged": false
    }
  ],
  "count": 1
}

Example 3: Dry Run Delete

INPUT:
{
  "operation": "delete-branch",
  "parameters": {
    "branch_name": "feat/old-feature",
    "location": "both",
    "dry_run": true
  }
}

OUTPUT:
{
  "status": "success",
  "operation": "delete-branch",
  "dry_run": true,
  "would_delete": {
    "branch": "feat/old-feature",
    "location": "both",
    "merged": true
  }
}

Example 4: Delete Merged Branch Locally

INPUT:
{
  "operation": "delete-branch",
  "parameters": {
    "branch_name": "feat/123-old-feature",
    "location": "local",
    "force": false
  }
}

OUTPUT:
{
  "status": "success",
  "deleted_local": true,
  "deleted_remote": false,
  "fully_merged": true
}

Example 5: Force Delete Unmerged Branch

INPUT:
{
  "operation": "delete-branch",
  "parameters": {
    "branch_name": "experiment/test",
    "location": "both",
    "force": true
  }
}

OUTPUT:
{
  "status": "success",
  "deleted_local": true,
  "deleted_remote": true,
  "fully_merged": false,
  "forced": true
}

Example 6: Protected Branch Error

INPUT:
{
  "operation": "delete-branch",
  "parameters": {
    "branch_name": "main",
    "location": "local"
  }
}

OUTPUT:
{
  "status": "failure",
  "error": "CRITICAL: Cannot delete protected branch: main",
  "error_code": 10
}

Strategy 1: Conservative (Merged Only)

  • Only delete fully merged branches
  • Verify merge to default branch
  • Keep all unmerged work
  • Safest approach

Strategy 2: Time-Based (Inactive)

  • Delete branches inactive for N days
  • Warning: May delete unmerged work
  • Requires careful threshold selection
  • Good for team coordination

Strategy 3: Combined (Merged + Inactive)

  • Delete merged branches regardless of age
  • Delete unmerged branches only if very old (90+ days)
  • Balanced approach
  • Recommended for most teams

Strategy 4: Aggressive (Force Everything)

  • Delete all stale branches
  • Use with caution
  • Only for cleanup operations
  • Requires team communication

Best Practices:

  1. Always start with dry-run
  2. Review list before deletion
  3. Communicate with team
  4. Keep protected branch list updated
  5. Run cleanup regularly (weekly/monthly)

Called By:

  • repo-manager agent - For programmatic cleanup operations
  • /repo:cleanup command - For user-initiated cleanup
  • Scheduled maintenance tasks
  • DevOps automation scripts

Calls:

  • repo-common skill - For configuration loading
  • handler-source-control-{platform} skill - For platform-specific operations

Integrates With:

  • Branch protection rules
  • Git garbage collection
  • Repository maintenance
  • Team workflows

Context Efficiency

This skill handles branch cleanup:

  • Skill prompt: ~600 lines
  • No script execution in context (delegated to handler)
  • Clear safety checks
  • Comprehensive reporting

By separating cleanup operations:

  • Independent cleanup testing
  • Clear safety boundaries
  • Better audit trail
  • Protected branch enforcement
  • Dry-run support