Because I learned that 10000 offline ephemeral self hosted runners is a funny thing
A command-line tool to delete GitHub self-hosted runners from a repository using the GitHub API.
- ποΈ Batch deletion of all self-hosted runners in a repository or organization
- π’ Organization-level support - Delete all runners across an entire organization
- π Dry run mode to preview what would be deleted
- π΄ Offline-only mode - Filter to delete only offline runners
- π Pagination support to handle repositories with many runners
- π¨ Colorful output with clear status indicators
- β Safety prompts to prevent accidental deletions
- π Token-based authentication with environment variable support
git clone <repository-url>
cd ghr-destroy
cargo build --releaseThe binary will be available at target/release/ghr-destroy.
ghr-destroy --owner myorg --token ghp_your_token_hereghr-destroy --owner myorg --repo myrepo --token ghp_your_token_hereexport GITHUB_TOKEN=ghp_your_token_here
ghr-destroy --owner myorg --repo myrepoPreview what would be deleted without actually deleting:
ghr-destroy --owner myorg --repo myrepo --dry-runDelete all runners without confirmation prompts:
### Delete Only Offline Runners
Target only offline runners for deletion:
```bash
# Organization level - offline runners only
ghr-destroy --owner myorg --offline-only
# Organization level
ghr-destroy --owner myorg --yes
# Repository level
ghr-destroy --owner myorg --repo myrepo --yes
### Custom Pagination
Fetch more runners per page (default is 100):
```bash
ghr-destroy --owner myorg --repo myrepo --per-page 50| Argument | Short | Description | Required | Default |
|---|---|---|---|---|
--owner |
-o |
GitHub organization or username | Yes | - |
--repo |
-r |
Repository name (optional - omit for org-level) | No | - |
--token |
-t |
GitHub personal access token | Yes* | - |
--dry-run |
- | Preview mode, don't delete | No | false |
--offline-only |
- | Only delete offline runners | No | false |
--yes |
-y |
Skip confirmation prompts | No | false |
--per-page |
- | Runners per page for API calls | No | 100 |
*Required unless set via GITHUB_TOKEN environment variable
Your GitHub personal access token needs the following permissions:
admin:org- To manage organization runnersrepo- To access repository information
repo- Full repository access
- Go to GitHub Settings β Developer settings β Personal access tokens
- Click "Generate new token (classic)"
- Select the required scopes listed above
- Copy the generated token
This tool uses the following GitHub REST API endpoints:
- List runners:
GET /repos/{owner}/{repo}/actions/runners - Delete runner:
DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}
ghr-destroy -o myorg -r myrepo -t ghp_token123ghr-destroy -o myorg -r myrepo -t ghp_token123 --dry-runghr-destroy -o myorg -r myrepo -t ghp_token123 -y# Organization level - offline runners only (with confirmation)
ghr-destroy -o myorg -t ghp_token123 --offline-only
# Repository level - offline runners only (dry run)
ghr-destroy -o myorg -r myrepo -t ghp_token123 --offline-only --dry-run
# Organization level - offline runners only (skip confirmation)
ghr-destroy -o myorg -t ghp_token123 --offline-only --yesThe tool handles various error scenarios:
- Invalid token: Clear error message about authentication
- Repository not found: 404 errors are reported clearly
- Insufficient permissions: Permission errors are explained
- Network issues: Connection and timeout errors are handled
- Rate limiting: API rate limit errors are reported
The tool provides colorful, informative output:
- π Busy runners are indicated with a spinning icon
- π€ Idle runners are shown with a sleep icon
- π’ Online status in green
- π΄ Offline status in red
- π‘ Unknown status in yellow
- Confirmation prompt before deletion (unless
--yesis used) - Dry run mode to preview operations
- Clear output showing exactly what will be deleted
- Error reporting for failed deletions
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
[Add your license information here]
--dry-run first!