Automatically detect and notify about stale feature branches in your GitHub repository. This GitHub Action identifies inactive branches and creates (or updates) GitHub Issues to notify branch owners to take action.
By: Arunkumar Amarnath (@amarun22)
Part of: amarun22-studio
This self-contained action scans all branches in your repository, identifies those with no recent commits, and notifies the branch authors via GitHub Issues. It includes:
- β Configurable inactivity threshold
- β Smart collaborator validation
- β Automatic issue creation/updates
- β Automatic reconciliation and stale-issue auto-close
- β Dry-run mode for safe testing
- β Exclude specific branches by policy
- β PR metadata in issue body
- β Optional IssueOps: delete branches directly from an issue comment
- β Diff summary (ahead/behind, files changed) included in every issue automatically
- β No dependency on private repositories
Best for:
- repositories that want a lightweight stale-branch policy without a separate app
- teams that want issue-based follow-up instead of immediate branch deletion
- scheduled hygiene checks that still support safe manual dry runs
Delete from issue comments is optional and enabled only if you add the separate IssueOps workflow.
Add this action to a scheduled workflow and grant the required permissions:
name: Stale Branch Check
on:
schedule:
- cron: '0 9 * * 0'
workflow_dispatch:
jobs:
stale-branches:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: read
steps:
- uses: amarun22-studio/stale-branch-notifier-action@v1
with:
days_inactive: 15
dry_run: 'false'If you want centralized configuration, pass repository variables into the inputs.
- Issues must be enabled in the target repository.
- The workflow job must grant
contents: read,issues: write, andpull-requests: read. - The action runs with the caller repository token, so it works for repositories where the workflow itself has access.
Add this to your GitHub Actions workflow:
- name: Run Stale Branch Notifier
uses: amarun22-studio/stale-branch-notifier-action@v1
with:
days_inactive: 15
dry_run: false
watchers: alice,bobname: Stale Branch Check
on:
schedule:
- cron: '0 9 * * 0' # Every Sunday at 09:00 UTC
workflow_dispatch:
inputs:
days_inactive:
description: 'Days of inactivity threshold'
required: false
default: '15'
dry_run:
description: 'Dry run mode (no issues created)'
required: false
type: boolean
default: false
jobs:
check-stale:
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: read
steps:
- name: Run Stale Branch Notifier
uses: amarun22-studio/stale-branch-notifier-action@v1
with:
days_inactive: ${{ github.event.inputs.days_inactive || '15' }}
dry_run: ${{ github.event.inputs.dry_run || 'false' }}
watchers: team-lead,devops-admin
excluded_branches: main,production,develop| Input | Description | Required | Default |
|---|---|---|---|
days_inactive |
Days of inactivity threshold | No | 15 |
dry_run |
Log actions without creating issues | No | false |
watchers |
Comma-separated GitHub usernames to CC | No | `` |
excluded_branches |
Comma-separated branches to exclude | No | `` |
days_inactive
Number of days a branch can be inactive before it's considered stale. Default: 15 days.
with:
days_inactive: 30dry_run
When true, logs what would happen without creating any GitHub Issues. Perfect for testing.
with:
dry_run: 'true'watchers
Comma-separated list of GitHub usernames to receive @mention in every issue for visibility.
with:
watchers: alice,bob,charlieexcluded_branches
Comma-separated list of exact branch names to skip from scanning. The main branch is always excluded.
with:
excluded_branches: main,production,release/latest| Output | Description |
|---|---|
stale_branches_found |
Number of stale branches detected |
issues_created |
Number of issues created or updated |
issues_auto_closed |
Number of open stale issues auto-closed during reconciliation |
- name: Run Stale Branch Notifier
id: notifier
uses: amarun22-studio/stale-branch-notifier-action@v1
with:
days_inactive: 15
- name: Report results
run: |
echo "Stale branches found: ${{ steps.notifier.outputs.stale_branches_found }}"
echo "Issues created: ${{ steps.notifier.outputs.issues_created }}"
echo "Issues auto-closed: ${{ steps.notifier.outputs.issues_auto_closed }}"When a stale branch is detected, an issue like this is created:
[Stale Branch] Action needed: 2 inactive branches β octocat
πΏ Stale Feature Branch β Action Required
Hi @octocat,
The following feature branch(es) associated with your account have had
no commits for 15+ days and need attention:
| Branch | Last Commit | Inactive For | Open PR(s) |
|--------|-------------|--------------|-----------|
| feature/login-redesign | 2026-03-18 | 23 days | #421 |
| feat/api-v2 | 2026-03-12 | 29 days | None |
### Diff Summary (vs `main`)
| Branch | Commits Ahead | Commits Behind | Files Changed | Full Diff |
|--------|---------------|----------------|---------------|-----------|
| feature/login-redesign | 12 | 3 | 9 (+214/-67) | Compare |
| feat/api-v2 | 4 | 18 | 3 (+42/-11) | Compare |
### Please take one of these actions for each branch:
- π¨ Push new commits (work in progress)
- π Open a Pull Request (ready for review)
- ποΈ Delete the branch (abandoned)
β οΈ Stale branches add clutter and make navigation harder.
---
π’ CC for visibility: @alice @bob
_Auto-generated by Stale Branch Notifier Β· 2026-04-13_
IssueOps delete is optional. By default, this action only detects stale branches and creates or updates issues.
If you add the optional IssueOps workflow, authorized users can delete a branch directly from the issue without leaving GitHub.
Tip: Diff information (commits ahead/behind, files changed, link to full diff) is included automatically in every issue when it is created β no command needed.
| Comment | Effect |
|---|---|
/delete-branch <branch-name> |
Deletes the branch (with safety checks) |
One branch per command.
| Commenter | Allowed? |
|---|---|
| Issue assignee (the branch owner) | β Yes |
Configured watchers |
β Yes |
| Collaborator with write/maintain/admin access | β Yes |
| Anyone else | β No |
The action enforces all of these before deleting:
- The branch must be listed in the issue's table β prevents deleting unrelated branches
- The branch must not be the default branch or in the
excluded_brancheslist - The branch must not have any open pull requests β must be closed/merged first
- Permission and safety checks run β any failure posts an explanatory comment and stops.
- The branch is deleted.
- A confirmation comment is posted.
- If other branches from the issue are still active, the issue stays open with a list of what remains.
- Once all branches are resolved, the issue is closed automatically.
Add a second workflow file alongside your existing stale-branch check, or copy examples/issueops-delete-branch.yml:
name: Stale Branch IssueOps
on:
issue_comment:
types: [created]
permissions:
contents: write # delete branches
issues: write # post comments and close issues
pull-requests: read # check open PRs before deletion
jobs:
handle-command:
runs-on: ubuntu-latest
if: |
!github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/delete-branch')
steps:
- name: Handle IssueOps command
uses: amarun22-studio/stale-branch-notifier-action@v1
with:
watchers: ${{ vars.STALE_BRANCH_NOTIFIER_WATCHERS || '' }}
excluded_branches: ${{ vars.STALE_BRANCH_NOTIFIER_EXCLUDED_BRANCHES || '' }}Note: The
issue_commentevent fires for both issue comments and PR comments. Theif:condition and the action itself both guard against PR comments β only issues labeledstale-branchare processed.
Composite actions cannot read the caller repository's vars context automatically, so the recommended pattern is to pass repository variables into the action inputs from your workflow:
-
Go to Settings β Secrets and variables β Variables
-
Create these variables:
STALE_BRANCH_NOTIFIER_DAYS_INACTIVE=15STALE_BRANCH_NOTIFIER_WATCHERS=alice,bobSTALE_BRANCH_NOTIFIER_EXCLUDED_BRANCHES=main,production
-
Pass those repository variables into the action inputs in your workflow:
- uses: amarun22-studio/stale-branch-notifier-action@v1
with:
days_inactive: ${{ vars.STALE_BRANCH_NOTIFIER_DAYS_INACTIVE }}
watchers: ${{ vars.STALE_BRANCH_NOTIFIER_WATCHERS }}
excluded_branches: ${{ vars.STALE_BRANCH_NOTIFIER_EXCLUDED_BRANCHES }}Inside this action, precedence is:
- Action input value supplied by your workflow
- Built-in default (
15days for threshold, empty lists for watchers/exclusions)
Test the action safely without creating issues:
- uses: amarun22-studio/stale-branch-notifier-action@v1
with:
days_inactive: 15
dry_run: 'true' # Log only, no issues createdCheck the workflow logs to see what would happen.
This repository includes a manual-only release workflow at .github/workflows/release-manual.yml.
Use it from Actions -> Manual Release -> Run workflow (on main) with:
version: SemVer value like1.2.0orv1.2.0update_major_tag: when true, moves the major tag (for example,v1) to this releasedry_run: validates and prepares tags without pushing or creating a release
What it automates:
- Validates version input format
- Ensures workflow is run from the default branch
- Creates the new version tag (for example,
v1.2.0) - Optionally updates the major tag pointer (for example,
v1) - Pushes tags and creates a GitHub Release with generated notes
This action requires the following permissions:
permissions:
contents: read # Read branch metadata
issues: write # Create/update issues
pull-requests: read # Read PR informationAdd these to your workflow or job-level permissions.
- Scans all branches - Lists all branches in the repository
- Checks last commit - Determines when each branch was last updated
- Identifies stale branches - Compares against your threshold (default: 15 days)
- Validates authors - Checks if branch author is an active collaborator
- Creates/updates issues - Groups branches by author and creates GitHub Issues
- CC watchers - Mentions configured watchers for visibility
- Handles idempotency - Updates existing issues instead of creating duplicates
- Default branch is always excluded (e.g.,
main) - Excluded branches from input are skipped
- Other branches are evaluated for staleness
- Active collaborators get assigned the issue
- Repo owners with implicit admin access get assigned
- Deleted/removed authors β issue assigned to watchers + author info in body
- External committers (no GitHub login) β assigned to watchers
- Same stale branch on next run β Existing issue updated + refresh comment
- Branch no longer stale β Issue auto-closed with clear comment
- Branch deleted β Issue auto-closed
- Branch excluded by policy β Existing stale issue auto-closed as non-actionable
- Check if any branches are actually older than your threshold
- Run with
dry_run: trueto see logs - Verify permissions are set correctly
- Confirm the branch author is an active collaborator
- Check watchers are configured if author is external
- Review workflow logs for validation details
- Ensure
contents: read,issues: write,pull-requests: readpermissions - Check GitHub token has access to the repository
- Review workflow logs for specific errors
- amarun22-studio - Collection of GitHub Actions and development tools
Found a bug? Have a feature request? Issues and pull requests are welcome!
Visit the GitHub repository for:
- Issue tracking
- Pull request guidelines
- Bug reports and feature requests
MIT - see LICENSE file
For questions, issues, or feature requests:
- GitHub: @amarun22
- Issues: Open an issue
Made with β€οΈ by Arunkumar Amarnath