Skip to content

Repository files navigation

Mac Runner

Simple Mac menu bar app and CLI for managing GitHub Actions self-hosted runners.

Features

  • πŸƒ Run multiple GitHub Actions runners on a single Mac
  • πŸ–₯️ Dual CLI + GUI β€” manage runners from terminal or menu bar
  • πŸ”‘ gh CLI integration β€” no manual PAT tokens, uses your existing gh auth
  • πŸ”’ Hybrid isolation β€” choose user isolation (macOS runners) or container isolation (Linux workflows)
  • 🎭 Headless by default β€” runners run without GUI access (enable when needed for visual testing)
  • ⏸️ Pause/Resume all runners with one click
  • 🎯 Perfect for when you need your Mac's resources for intensive work
  • πŸ“Š Monitor runner status from menu bar
  • ⚑ Native Mac app, lightweight and fast
  • πŸ€– Fully automated setup β€” downloads and configures runners automatically
  • 🧹 Disk pressure cleanup β€” safely reclaim idle runner workspaces and CI caches

Why?

GitHub Actions self-hosted runners are great, but:

  • Official runner only supports one instance per machine
  • No easy way to pause runners when you need CPU/memory
  • Managing multiple repos means multiple runner processes
  • No visual indication of runner status

Mac Runner solves this.

Installation

Homebrew

brew install --cask omniaura/tap/mac-runner

Direct Download

Download the latest DMG from Releases

Prerequisites

  • macOS 13+ (macOS 15+ for user isolation, macOS 26+ for container isolation)
  • gh CLI installed and authenticated (gh auth login)
  • Apple Silicon Mac (for container isolation)

Quick Start

GUI

  1. Launch Mac Runner from Applications or run open /Applications/MacRunner.app β€” it appears in the menu bar
  2. Click "Add Runner"
  3. Browse your repos (fetched via gh), pick one
  4. Runner downloads, configures, registers, and starts automatically

CLI

# Check GitHub auth
mac-runner auth

# Add a runner (downloads, configures, starts in background)
mac-runner add owner/repo --name my-runner --labels macos,mac-runner

# Add a runner with GUI access (for visual tests, Xcode UI tests, etc.)
mac-runner add owner/repo --enable-gui

# List runners
mac-runner list

# Start/stop
mac-runner start my-runner
mac-runner stop my-runner

# Remove (deregisters from GitHub and deletes the runner's workspace)
mac-runner remove my-runner

# Status summary
mac-runner status

# Preview or run cleanup (active runner data is always skipped)
mac-runner cleanup --dry-run
mac-runner cleanup

# Remove every runner and every file Mac Runner created
mac-runner uninstall --dry-run
mac-runner uninstall

Runners started via CLI persist in the background β€” they survive the terminal session. Stop and start them from any terminal or from the GUI.

Disk Cleanup

GitHub Actions jobs can leave large workspaces and dependency caches behind. mac-runner cleanup removes the contents of stopped runners' _work directories plus known npm, SwiftPM, Homebrew, Go, Cargo, Gradle, and Xcode caches. If any runner is active, its workspace is skipped and shared caches are preserved.

In Settings, enable Clean CI Data When Disk Space Is Low and choose a minimum free-space target. Mac Runner checks at most once per hour and only cleans when available space falls below that target. Automatic cleanup is off by default.

Use mac-runner cleanup --workspaces-only to preserve all shared caches.

Uninstalling

mac-runner uninstall tears down a Mac Runner installation completely. It stops running runners, deregisters them from GitHub, and deletes every location Mac Runner writes to:

Location Contents
~/.mac-runner Runner workspaces β€” the extracted runner release and its _work checkout, typically >1 GB each
~/Library/Application Support/MacRunner config.json, PID files, container kernel
~/Library/Preferences/{com.omniaura.mac-runner,mac-runner}.plist App and CLI preferences
~/Library/HTTPStorages/*, ~/Library/Caches/* Cached update checks
~/Library/Application Support/CrashReporter, ~/Library/Logs/DiagnosticReports Crash and diagnostic reports

Deregistering first matters: deleting a runner's credentials without telling GitHub leaves it listed as a permanently offline runner in the repository's Actions settings.

mac-runner uninstall --dry-run      # show exactly what would be deleted, and how much space
mac-runner uninstall                # prompts before deleting
mac-runner uninstall --yes          # skip the prompt
mac-runner uninstall --include-app  # also delete MacRunner.app and the mac-runner symlink
mac-runner uninstall --keep-runners # delete local files but leave GitHub registrations

Uninstall also removes orphaned workspaces β€” directories left on disk by earlier versions that deleted a runner from config.json without deleting its files. If you have been using Mac Runner for a while, --dry-run is worth running even if you have no runners configured.

If you installed with Homebrew, remove the app itself with brew uninstall --cask mac-runner. Use brew uninstall --zap --cask mac-runner to remove the app and all of its data in one step.

Run mac-runner uninstall first if you have runners configured. Homebrew only deletes local files: it cannot deregister your runners from GitHub, and it cannot reach workspaces owned by a dedicated service user, which live outside your home directory. Runners removed without deregistering stay listed in the repository's Actions settings as permanently offline.

CI/CD: Self-Hosted Runner with Automatic Cloud Fallback

Mac Runner uses a pattern that automatically routes CI jobs to your self-hosted Mac when it's online, and falls back to GitHub-hosted cloud runners when it's not. This means pushes to main always build, regardless of whether your Mac is on.

How It Works

The release workflow uses mikehardy/runner-fallback-action to query the GitHub API for available self-hosted runners before the build job starts:

jobs:
  preflight:
    runs-on: ubuntu-latest
    outputs:
      runner: ${{ steps.runner.outputs.use-runner }}
    steps:
      - name: Select runner
        id: runner
        uses: mikehardy/runner-fallback-action@v1
        with:
          primary-runner: mac-runner
          fallback-runner: macos-latest
          fallback-on-error: true
          github-token: ${{ secrets.RUNNER_TOKEN }}

  build:
    needs: preflight
    runs-on: ${{ fromJson(needs.preflight.outputs.runner) }}
    steps:
      - run: echo "Running on the best available runner"
Mac online Runner used Cost
Yes mac-runner (self-hosted) Free
No macos-latest (cloud) GitHub Actions minutes

This pattern is useful for any project that wants fast, free self-hosted builds when available, with reliable cloud fallback. See the community discussion for background on why this isn't built into GitHub Actions natively.

Token Setup

The runner-fallback-action queries the GitHub REST API to check runner availability. This requires a token with admin read access β€” the default GITHUB_TOKEN does not have this permission.

  1. Create a fine-grained Personal Access Token:
    • Repository access: select "Only select repositories" and pick your repo
    • Permissions β†’ Repository β†’ Administration: Read-only
  2. Add it as a repository secret named RUNNER_TOKEN:
    gh secret set RUNNER_TOKEN

Note: If the token is missing or invalid, fallback-on-error: true ensures the workflow still runs β€” it just falls back to cloud runners.

References

Isolation Modes

Mac Runner supports three isolation modes to protect your development environment:

1. No Isolation (Default)

  • Runners execute directly as your current user
  • Simple setup, works on any macOS version
  • ⚠️ Not recommended for untrusted workflows β€” CI jobs have full access to your files

2. User Isolation (macOS 15+)

  • Each runner runs as a dedicated system user (e.g., _macrunner)
  • Prevents CI jobs from accessing your files, credentials, and desktop
  • Best for macOS-based workflows
  • How to enable:
    # CLI
    mac-runner add owner/repo --isolation user
    
    # GUI
    Settings β†’ Isolation Mode β†’ Dedicated User

3. Container Isolation (macOS 26+, Apple Silicon)

  • Runs Linux workflows in isolated, lightweight virtual machines
  • Uses Apple's Containerization framework
  • Sub-second startup times with Rosetta 2 for linux/amd64 emulation
  • Best for Linux-based workflows, Docker builds, cross-platform testing
  • Requirements: macOS 26+, Apple Silicon, Linux kernel 6.14.9+
  • How to enable:
    # CLI
    mac-runner add owner/repo --isolation container
    
    # GUI
    Settings β†’ Isolation Mode β†’ Container

Per-Runner Isolation Override

You can set a global default isolation mode and override it per-runner:

# Set global default to user isolation
mac-runner settings --isolation user

# Add a Linux runner with container isolation
mac-runner add owner/linux-project --isolation container

# Add a macOS runner with no isolation (for trusted workflows)
mac-runner add owner/trusted-project --isolation none

In the GUI, each runner displays its isolation mode with an icon:

  • πŸ”“ No isolation
  • πŸ‘€ User isolation
  • πŸ“¦ Container isolation

GUI Access

By default, runners operate in headless mode β€” they run without access to the GUI (display, windows, etc.). This is optimal for most CI jobs and prevents interference with your desktop.

When to enable GUI access:

  • Visual testing (screenshot comparisons, E2E tests with browsers)
  • Xcode UI tests
  • iOS Simulator tests
  • macOS app GUI automation

How to enable:

# CLI
mac-runner add owner/repo --enable-gui

# GUI
Add Runner β†’ Enable GUI Access (toggle)

Note: GUI sessions are currently shared across runners. For full isolation (separate GUI session per runner), see Issue #27.

Planned Features

  • Auto-provision CI tools (node, npm, gh) in runner environments
  • Automatic pause when battery low
  • Pause during specific hours
  • Resource usage monitoring
  • Notifications for job starts
  • Custom container images for containerized runners

Architecture

  • Swift 6 / SwiftUI β€” Native Mac app
  • Menu Bar Interface β€” Always accessible, minimal UI
  • gh CLI β€” All GitHub API calls go through gh (auth, repos, runner tokens, CRUD)
  • PID-based process management β€” Runners persist across CLI sessions
  • Dual entry point β€” main.swift dispatches to CLI handler or SwiftUI app

Development

# Build
swift build

# Run CLI
.build/debug/mac-runner --help

# Run GUI (no args)
.build/debug/mac-runner

# Test
swift test

Contributing

We use Conventional Commits for automatic versioning:

  • feat: β€” New feature (minor bump)
  • fix: β€” Bug fix (patch bump)
  • chore: β€” No release

See CONTRIBUTING.md for full guidelines.

License

MIT

About

Mac menu bar app for managing GitHub Actions self-hosted runners

Resources

Contributing

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages