guide
Codemany, explained.
Your Mac is idle.
Your CI costs $200+ a month.
Run it on your Mac.
- The problem
- What it is
- What happens
- When things go wrong
- Use cases
- Problem → fix
- Money
- Security
- Requirements
- Glossary
- Questions
The problem today
- CI minutes are billed. GitHub-hosted runners are billed per minute, and GitHub rounds each job up to a whole minute.
- macOS minutes cost more. GitHub's published per-minute price for a macOS runner is higher than for a Linux runner, and iOS and macOS apps need macOS runners.
- Meanwhile, your own Mac sits idle. The Mac on your desk, or a spare Mac mini, already has your Xcode and your tools.
- Do-it-yourself runners are fiddly. A self-hosted runner has to be registered with a token, and a long-lived one leaks state between jobs: dirty work directories, stale caches, half-killed simulators. Runners die, and nobody notices until jobs sit in
queued. The Mac goes to sleep mid-job and GitHub marks the job lost. A running job cannot be paused, so updates have to wait for jobs, and there is no single place to see which runners are busy, idle or dead.
What Codemany is
Codemany is a Mac app that runs your GitHub Actions jobs on your own Mac. It keeps a pool of self-hosted runners sized to your Mac: every job gets a fresh, single-use runner, and the runner is recycled after the job. GitHub still schedules the jobs and shows the logs.
- Download Codemany and drag it to Applications. It is signed and notarized by Apple.
- Connect GitHub: GitHub CLI or a token.
- Pick repos.
- Change one line in a workflow:
runs-on: [self-hosted, codemany]. From then on your Mac keeps running jobs.
What you get in the app:
- Workflows, runs, jobs and steps, the way GitHub models them, with live step logs for jobs on your Mac.
- Overview: jobs running, jobs waiting (oldest first), jobs done, and how many runners your Mac holds.
- Savings: roughly what the jobs on your Mac would have cost on GitHub-hosted runners (see Money).
- Menu bar status, a desktop widget that shows whether your Mac is kept awake, and a LAN dashboard at
http://codemany.local:8825that any browser on your network can open. The app shows a QR code ("View on phone") to open it on a phone. - Integrations for what jobs need: start Docker or keep it running, install Colima, install allowlisted Homebrew packages (CocoaPods, fastlane, gh, xcbeautify and a few more), and pre-warm the tool cache.
What actually happens on your Mac
- One runner per job. For each runner, Codemany asks GitHub for a just-in-time registration. The runner takes exactly one job and exits. Its directory is wiped and re-cloned from a clean copy of GitHub's runner (an instant APFS clone of a checksummed download), then a new runner registers. One credential mints runners forever: no registration tokens to juggle.
- Sized to your Mac. By default each runner budgets 4 GB of memory, 15 GB of disk and 2 performance cores, and 4 GB of memory plus 20 GB of disk stay reserved for macOS. That is 1 runner on an 8 GB Mac and 3 on a 16 GB one, never fewer than 1 or more than 32.
- Several repos share the runners. Each repo keeps a warm runner while there are runners to spare, and idle runners move to the repo whose jobs are waiting. Busy runners are never touched.
- It runs in the background. Jobs run in Codemany's background service, which keeps running when the app is closed, quits or updates. A headless Mac mini whose app is not running keeps working.
- It keeps your Mac awake while jobs run (or while runners are online, or never: your choice), and lets it sleep again 2 minutes after the last job.
- Jobs use your tools. Jobs get what is installed on your Mac: Xcode, Homebrew packages, Docker and so on. Codemany checks each tool every 30 minutes, adds an
xcode-…label for the selected Xcode and adockerlabel while Docker is running, and cachesactions/setup-*downloads between jobs.
When things go wrong
- The Mac sleeps or the lid closes. Codemany holds macOS's keep-awake assertion while jobs run. It cannot stop a laptop on battery from sleeping when the lid closes without an external display, Low Power Mode, or someone choosing Sleep, so it warns about those ("On battery: closing the lid stops jobs"). After a sleep it records which jobs were lost and says so once.
- All runners are busy. New jobs wait on GitHub. Codemany lists them oldest first and highlights waits over 15 minutes. GitHub, not Codemany, picks which waiting job a free runner takes.
- A runner crashes. Its registration is deleted and it backs off (5 seconds, growing to 5 minutes), then a fresh one starts. A dead runner is a counter on the dashboard, not an incident.
- A runner goes offline. When GitHub reports an idle runner offline for longer than a grace period (2 minutes by default), Codemany kills it, along with any leftover child processes such as simulators, and starts a fresh one.
- The disk runs low. Free disk is checked before every runner starts. Below the reserve plus one runner's share, the runner waits with a "low disk space" error instead of starting a job that could fill the disk.
- Docker or Xcode changes. Labels are fixed when a runner registers, so the next runners pick up the change: the
dockerlabel appears while Docker answers and goes away when it stops. With "Keep Docker running" on, Codemany restarts Docker when it stops. - An update arrives while jobs run. Installing the update never stops a running job. The background service restarts onto the new version only after running jobs finish.
- GitHub rate-limits or denies access. Polling pauses until the limit lifts. If the token is revoked, running jobs keep running and new runners retry every 5 minutes; reconnect GitHub and the next retry picks up the new token.
- The network drops. Errors are recorded and shown, and Codemany keeps retrying. Errors from GitHub never stop a running job. The license keeps working for 7 days offline.
- The trial or subscription ends. New runners stop with "Codemany Pro license required". Jobs already running are never stopped.
- A repo turns public. Codemany re-checks every repo every 30 minutes. A public repo is refused unless you opted in for it (see Security).
Use cases
- iOS and macOS apps. Builds and tests with your own Xcode and simulators. Ask for a version with
runs-on: [self-hosted, codemany, xcode-26.0]. - Swift packages and any test suite your Mac can run: Go, Node and Python downloads from
actions/setup-*are cached between jobs, and the pre-warm fetches the versions your workflows ask for. - Docker builds on jobs that ask for
docker, with Docker Desktop or Colima. - Many parallel jobs. A matrix or a monorepo's jobs spread over all the runners your Mac holds.
- Indie developers and side projects who already own a Mac, and small teams with a spare Mac or Mac mini.
Who it is not for
- Jobs that need Windows or an x86 machine: Codemany's runners are macOS on Apple silicon (labels
macOSandARM64). - Running untrusted code. A self-hosted runner is not a sandbox: jobs run as your macOS user. Public repos, where anyone can open a pull request from a fork, need the opt-in and care described below.
- Spreading one queue over several Macs: each Mac runs its own runners, and a subscription covers 1 Mac.
Each problem, and how Codemany fixes it
| Problem | Fix |
|---|---|
| Paying for CI minutes on hosted runners | Jobs run on your Mac for a flat $20/month |
| Registration tokens | One-time setup; Codemany mints a just-in-time runner for every job |
| State leaking between jobs | A fresh runner per job; its directory is wiped after |
| Runners dying unnoticed | Crashed runners back off and are recycled; offline runners are replaced |
| The Mac sleeping mid-job | Keep-awake while jobs run, warnings, and a record of lost jobs |
| Updates interrupting jobs | Updates wait for running jobs |
| No way to see what runs | Workflows, runs, jobs and live step logs in the app, the menu bar, a widget and the LAN dashboard |
| Too many or too few runners | Runner count sized to your Mac's memory, disk and cores |
Saving money
Your Mac is idle. Your CI costs $200+ a month. Run it on your Mac.
- Codemany costs $20/month per Mac, after a 14-day free trial with no card. No per-minute billing. No seats.
- Jobs run on your Mac instead of on GitHub-hosted runners. GitHub sets its own terms for self-hosted runners; any GitHub charges for them still apply on GitHub's side.
- The Savings page estimates what the jobs on your Mac would have cost on GitHub-hosted runners, over the last 24 hours, 7 days and 30 days, job by job. Each job's run time is rounded up to a whole minute, as GitHub bills, and priced at GitHub's published per-minute rates: as a macOS job when its names mention an Apple tool or platform (Xcode, Swift, iOS, simulator and so on), else as a cheaper Linux job. Public repos count as free on standard runners. Free minutes in your GitHub plan are not subtracted, so it is a ballpark, labelled as one.
Security and privacy, briefly
- A self-hosted runner is not a sandbox. Only run workflows you would run in your own terminal. Jobs run as your macOS user; a dedicated Mac or macOS user limits what a job can reach.
- Public repos are opt-in, per repo. Codemany also refuses one whose fork pull-request approval setting is too weak or whose workflows use risky triggers, and re-checks every 30 minutes.
- What leaves your Mac: API calls to GitHub, made directly with your token; to us, your GitHub id, login and email when you start a trial or subscribe, a hashed device id, your Mac's name and a daily license check; to Stripe, your payment details. Your GitHub token stays in Codemany's Keychain vault on your Mac. No analytics.
- The LAN dashboard is read-only from other devices unless you set an admin token.
More: Is it safe? and the Privacy Policy.
Requirements, pricing and support
- Requires: a Mac with Apple silicon on macOS 14 or later, and a GitHub account that can add self-hosted runners to the repository or organization (repo admin, or org owner).
- Pricing: $20/month per Mac, 14 days free, no card. 1 Mac per subscription; move it anytime. Cancel anytime. See Pricing.
- Download: codemany.com/download, with a sample workflow.
- Support: a direct message to @arpwal on X, or Report a Bug in the app. Release notes: changelog. Labels: runner labels.
Glossary
- Runner: the program that takes a job from GitHub and runs its steps. On Codemany, one runner on your Mac runs one job, then is replaced.
- Self-hosted runner: a runner on a machine you own instead of one GitHub hosts. Every one carries the
self-hostedlabel. - Ephemeral, just-in-time (JIT) runner: a runner registered for exactly one job, which exits after it.
- runs-on label: a job's
runs-onlists labels, and GitHub gives the job to a runner that has every one of them. - Workflow: a file in
.github/workflows. Run: one trigger of a workflow. Job: one unit that needs one runner. Step: one line of a job.
Questions people ask
Does my code leave my Mac?
Everything that touches your code runs on your Mac and reports to GitHub. Our servers only issue licenses and serve this site and the downloads.
Do I have to keep the app open?
No. Jobs run in the background service, which keeps running when the app is closed.
How many jobs run at once?
As many runners as your Mac holds: by default 1 on an 8 GB Mac and 3 on a 16 GB one.
What do I change in my workflows?
One line: runs-on: [self-hosted, codemany].
What happens when the trial ends?
New runners stop until you subscribe; jobs already running finish. Subscribing costs $20/month per Mac.
Can I move my subscription to another Mac?
Yes. 1 Mac per subscription; move it anytime.
Can it run jobs for an organization?
Yes, if your GitHub account can add self-hosted runners to the organization (org owner).