Yesterday we published a piece on a pattern we keep seeing: more teams move Claude Code, CodeGraph indexing, and Ollama onto a remote macOS node (see Cloud Mac vs local Mac). Many assume that once they “go cloud,” a git push will turn checks green. What actually happens: the agent edits happily in the terminal, GitHub Actions still runs on ubuntu-latest, xcodebuild fails immediately, or macOS jobs sit in queue for thirty minutes.
If you already run Claude Code on Cloud Mac, you may still see a Linux runner report xcodebuild: command not found after git push — the gap is usually not the agent but whether the repo has a macOS CI execution layer. This is not a Wikipedia entry for GitHub Runner, and it skips actions/runner registration (see the next article in the L1 series). It answers one thing: why Cloud Mac needs a dedicated L1 (GitHub Runner) instead of stopping at “a remote Mac that runs Claude Code.”
Cloud Mac AI Stack · series slogan
Claude Code produces Diff; GitHub Runner produces Fact.
Four outputs chain the stack: Context → Diff → Fact → Workflow. This article focuses on Fact (L1 · GitHub Runner).
What L1 does in the Stack
Claude Code (L3) answers “how to change the code.” GitHub Runner (L1) answers “after the change, can the org verify, sign, and ship it.” The agent proposes an answer; the Runner proves it in CI. Without L1, L3’s Diff does not become a Fact your team will merge.
Stack language: Context → Diff → Fact → Workflow
Right now the industry is loud about Claude Code, Cursor, MCP, and OpenHands — but quiet about a harder question: after the agent edits the repo, what turns the work into something the organization accepts? The usual vocabulary is Agent, Tool, CI, Automation. In the Cloud Mac AI Stack we chain four outputs (L2 inference is separate in the table below):
Memory chain (not runtime call order · see five-layer diagram below)
Context → Diff → Fact → Workflow
(MCP) (Claude Code) (Runner) (OpenHands)
| Layer | Responsibility | Component | Stack output |
|---|---|---|---|
| L0 | Infrastructure | Cloud Mac | Runnable surface (macOS node) |
| L1 | Execution | GitHub Runner | Fact |
| L2 | Inference | Ollama | Inference (private tokens, optional) |
| L3 | Coding | Claude Code | Diff |
| L4 | Tool connectivity | MCP | Context |
| L5 | Autonomous execution | OpenHands | Workflow |
The bottleneck is rarely “is the AI smart enough.” It is whether L1 exists as its own layer. Without a Fact layer, beautiful Context and Diff never become the green check on the merge button.
Coding layer vs execution layer: why “we moved to Cloud Mac” can still break CI
In workload split we separated model inference from agent execution: APIs stay on the vendor cloud; shell, Git, and tests run on macOS. One more split is easy to miss — not the pnpm test your agent runs in a session, but the repeatable, auditable build CI defines after push.
| Layer | Typical components | Trigger | Question answered |
|---|---|---|---|
| Coding / agent | Claude Code, Cursor Agent | You in terminal or IDE | “Finish this PR for me” |
| Execution / CI | GitHub Actions + self-hosted Runner | git push, PR, schedule |
“Does this commit build, test, and archive in a clean environment?” |
The gap shows up like this: Claude Code on Cloud Mac, tests green over SSH — but the workflow still targets a Linux runner, so the repo’s official truth stays red. For iOS teams that is not merely awkward; a TestFlight pipeline never starts on Linux. The execution layer must exist on its own, and it usually must match the same class of macOS environment you develop on.
Typical misjudgment: Claude Code says tests passed; the PR is red
We have seen the same incident shape in customer repos many times — easier to remember than a diagram:
- A developer runs Claude Code over SSH on Cloud Mac. The agent replies: “All tests have passed.”
- They
git pushand head into a meeting confident. - GitHub Actions fires; the workflow still has
runs-on: ubuntu-latest. - About ten minutes later PR checks are red. The log often starts with
xcodebuild: command not found— or there is no iOS job at all while Node tests go green on Linux.
The problem is not Claude Code. It is that Claude Code’s runtime ≠ CI’s runtime. “Passed” on macOS in a session was never reproduced by an org-trusted Runner on the same GitHub Actions pipeline. The Runner’s job is to turn session conclusions into auditable Facts on every push — ideally on the same GitHub Actions self-hosted runner macOS node when you want one machine for coding and CI.
Runner is not “renting another Mac”
Three misconceptions we want off the table:
- Not a Cloud Mac product pitch — renting hardware is L0; Runner is the always-listening GitHub job consumer on that base (labels, concurrency, workspace hygiene).
- Not a manual for GitHub’s hosted
macos-latest— hosted runners use a different billing, queue, and isolation model; self-hosted means you schedule jobs onto your node. - Not OpenClaw or OpenHands — OpenClaw notes cover orchestration and receipts (who triggers what, command order, audit). Runner is what actually runs
xcodebuildandfastlaneon the machine.
In one line: Cloud Mac supplies macOS capacity and egress; Runner wires that capacity into GitHub’s event model.
Without a Runner, Cloud Mac is a remote desktop. With a Runner, it is engineering infrastructure. If you remember one slogan from this series: Diff → Fact (blue callout above).
Cloud Mac AI Stack five-layer diagram (responsibility tiers)
The diagram shows how each layer carries weight in the org (not who calls whom at runtime). When you read L2–L5 articles, you can return to #stack-map here for reference.
Important: Stack ≠ call order
The diagram shows responsibility tiers in the org, not who calls whom at runtime. Therefore:
- Claude Code does not depend on Ollama — most teams use the Claude API at L3; L2 is optional private inference.
- MCP sits above Claude Code in the diagram because the Context layer feeds coding tools — not because the MCP server always boots before the CLI.
- Rollout order (L0→L1 before AI) is in § Stack rollout order, separate from bottom-up “load-bearing” relationships in the diagram.
Cloud Mac AI Stack five-layer diagram (responsibility tiers · bottom to top)
┌──────────────┐
│ OpenHands │ L5 · Workflow
└──────┬───────┘
│
┌──────▼───────┐
│ MCP │ L4 · Context
└──────┬───────┘
│
┌──────▼───────┐
│ Claude Code │ L3 · Diff
└──────┬───────┘
│
┌──────▼───────┐
│ Ollama │ L2 · Inference (optional)
└──────┬───────┘
│
┌──────▼───────┐
│ GitHub Runner│ L1 · Fact ← this article
└──────┬───────┘
│
┌──────▼───────┐
│ Cloud Mac │ L0 · infrastructure
└──────────────┘
When you read this diagram, remember: L0 carries all compute; L1 carries every Fact the org dares to trust; above that come Diff, Context, and Workflow. Ollama at L2 means private inference can sit on the base in parallel with L3 — not “you must run Ollama before opening Claude Code.”
Why we call it an “execution engine”: duties and a real chain
In the five-layer model, Runner owns L1. It does not compete with Claude Code on intelligence. It does three repeatable jobs:
- Accept repo events —
on: push,pull_request,workflow_dispatchdispatch jobs to labeled self-hosted runners. - Run native macOS toolchains —
xcodebuild,swift test,notarytool, signing and archive; hard requirements Linux runners cannot satisfy. - Stay decoupled from AI — agent edits ≠ CI pass; Runner turns “done editing” into artifacts, test reports, and deployable packages.
Below is the delivery path we sketch for iOS and Flutter teams targeting iOS — not abstract push → build, but what happens from AI coding to TestFlight:
Cloud Mac AI Stack · L1 execution chain (conceptual)
Claude Code (L3 coding layer, SSH / terminal)
│
│ git commit & push
▼
GitHub (webhook / Actions scheduler)
│
▼
GitHub Actions workflow
│
▼
GitHub Runner (L1 · self-hosted · macOS · ARM64)
│
├── xcodebuild (Debug / Release)
├── unit / UI tests
├── archive → .ipa
└── fastlane → TestFlight / internal distribution
Coding happens at L3; shipping binaries happens at L1. Skip a link and you get “the agent said done, App Store Connect has no build.”
In your repo’s workflow you usually only see runs-on labels for the Runner, not the machine-side registration. A typical GitHub Actions iOS build on a macOS ARM64 self-hosted runner looks like this (tokens, launchd, etc. in the next L1 article):
# .github/workflows/ios-ci.yml (excerpt) jobs: build-ios: runs-on: [self-hosted, macOS, ARM64, cloud-mac] steps: - uses: actions/checkout@v4 - name: Build & Test run: xcodebuild -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 16' test
People rarely search “GitHub Runner” — they search iOS CI and Mac mini runners
Engineers rarely search only the generic term GitHub Runner. They usually arrive with a concrete scenario, for example:
- GitHub Actions self-hosted runner macOS / macOS ARM64 runner / Apple Silicon runner
- GitHub Runner Mac mini / GitHub Actions Mac mini / self-hosted runner Mac mini
- iOS CI/CD self-hosted runner / GitHub Actions iOS build
- Cloud Mac CI, Xcode on CI, TestFlight automation
These phrases usually hide one architecture question: how to dispatch GitHub Actions jobs to your own Apple Silicon node instead of the default Linux pool or a queued hosted macos-latest. Cloud Mac (or an office Mac mini) provides the machine; registering a Runner and labeling it completes dispatch. This article is the why; registration steps are in the next L1 article.
If you are comparing “buy a Mac mini for the closet” vs “rent Cloud Mac,” Runner logic is the same — L0 ops and cost differ. See Mac mini vs Cloud Mac for iOS teams; a Runner cost comparison will follow later in the same series.
When Linux hosted runners are not enough — and when you do not need macOS at all
ubuntu-latest is excellent for web backends; it is a hard wall for Apple delivery. Comparison:
| Dimension | Hosted ubuntu-latest |
Cloud Mac self-hosted |
|---|---|---|
| Xcode / iOS build | ❌ not available | ✅ native |
| Apple Silicon / device arch alignment | misaligned | matches M-series dev machines |
| Same machine as Claude Code | ❌ heterogeneous | ✅ optional co-location |
| Queue and cost model | per-minute, peak queues | fixed node, good for daily CI |
Saying when something does not apply matters as much as when it does — otherwise readers think you sell Cloud Mac for everything. These projects usually stay on Linux runners:
- Next.js / static frontends — build in Node; no Xcode.
- Node APIs, Python FastAPI, Go services — Docker jobs on Linux are common with richer image ecosystems.
- Containerized backends —
docker buildplus deploy to Kubernetes; macOS irrelevant. - Small personal repos — CI a few times a month; hosted queue cost often beats a dedicated node.
Signals you need a macOS Runner: workflows mention Xcode, signing, notarize, iOS Simulator, TestFlight, or you are already evaluating Mac mini vs Cloud Mac for iOS infra. That article’s team model goes deeper; here we only anchor Runner at L1.
GitHub hosted macos-latest vs Cloud Mac self-hosted
Better fit for hosted macos-latest |
Better fit for Cloud Mac self-hosted |
|---|---|
| Occasional archive, <5 macOS jobs per month | Daily CI, fixed certs and provisioning profiles |
| OK with queue and per-minute billing swings | Want AI (Claude Code) and CI on one observable stack |
| No private network or fixed egress IP needs | Static IP, internal artifacts, or Runner sharing a machine with CodeGraph off-peak |
Rule of thumb (not a contract): when the team runs more than ~10 macOS jobs per week and half touch iOS signing or archive, we suggest moving L1 from “queue when needed” to “fixed Cloud Mac + self-hosted.” Below that, hosted macOS to prove the pipeline is often cheaper in attention.
Rollout order: Fact before Diff — not the same as the diagram
The five-layer diagram is responsibility tiers; deployment follows a different sequence (give the org Facts before stacking AI). Hot-topic articles often go Claude Code → MCP → “oh right, CI.” We recommend:
- L0 — Cloud Mac base (always-on macOS, egress, SSH).
- L1 — GitHub Runner so
push → green/redis repeatable (this article). - L2–L3 — Ollama, Claude Code on top of objective build results.
- L4–L5 — MCP, OpenHands after stable CI and coding environments.
Why: CodeGraph + an agent can touch eighteen files, but without stable macOS CI you never know if a missed edit explodes at archive time. Runner fixes machine acceptance first; AI stops performing in a sandbox.
Relation to Mac mini + Claude Code week-one notes: that piece is L3 coding experience; this one is L1 — the same box can run Runner at night and agents by day, but do not mix both layers in one tutorial.
Decision: who should treat Runner as the execution engine
No “Cloud Mac wins” slogan — only checks you can act on.
| Prioritize L1 (self-hosted on Cloud Mac) | May not need it |
|---|---|
| Native iOS / macOS app teams | Static sites, no native build |
| Flutter shipping iOS via Xcode toolchain | Flutter Android / web only |
| Claude Code on Cloud Mac but CI still on Linux | Node / Python APIs green on Linux Docker |
| Automating TestFlight / signing / notarize | Tiny personal projects, monthly releases |
| Fixed egress, private network, or same-machine CodeGraph index | Rare macos-latest archive is enough |
If two or more left-column rows match you, the next step is not another MCP server — it is getting L1 green. Steps land in the L1 series below.
L1 series: from execution engine to operable macOS CI
This article opens the Cloud Mac AI Stack · L1 series: why an execution engine must exist first. Next we take Runner from concept to something you can operate, and wire it to L2–L5:
| Part | Topic | Status |
|---|---|---|
| ① · this page | Why Runner is the execution engine (Diff → Fact) | Published |
| ② | Register a self-hosted Runner on Cloud Mac (step-by-step) | Next |
| ③ | One Runner serving Claude Code and CI (scheduling, same host) | Planned |
| ④ | Runner workspace cleanup and security isolation | Planned |
| ⑤ | Mac mini vs Cloud Mac Runner cost and queues | Planned |
L2 onward covers Ollama (Inference), Claude Code (Diff), MCP (Context), and OpenHands (Workflow) — ideally on top of an auditable Fact on every push. The five-layer responsibility map is here.
FAQ
What is the relationship between Cloud Mac and a self-hosted Runner?
Cloud Mac is the L0 base; Runner is the L1 process and policy on that base. Renting a machine ≠ having a Runner.
Can I run a Runner only on my MacBook?
Yes, but sleep, memory fights with agents, and IP churn hurt stability. Daily macOS CI usually means a 24/7 Cloud Mac node.
Must Runner and Claude Code be on the same machine?
No; co-location reduces “green in SSH, red in Actions” friction.
How is Runner different from OpenClaw?
Runner runs steps; OpenClaw orchestrates triggers and receipts. Both can share one Cloud Mac — see OpenClaw cloud automation.
How do GitHub Actions self-hosted runner macOS and Cloud Mac work together?
Install and register the Runner on Cloud Mac (L0), then use runs-on: [self-hosted, macOS, ARM64] (and your labels) so iOS CI/CD, xcodebuild, and TestFlight steps run on that node.
What does “Claude Code produces Diff; Runner produces Fact” mean?
Diff is session edits and subjective conclusions; Fact is green GitHub Checks, logs, and deployable artifacts. The org merges the latter. MCP produces Context; OpenHands produces Workflow — see § Stack language.
Ollama sits below Claude Code in the diagram — must Ollama run first?
No. The diagram is responsibility tiers, not a call chain. Claude Code typically uses the API; Ollama is optional L2 Inference — see the L2 Ollama architecture piece and § Five-layer diagram.
L1 series · next
Register a GitHub Actions self-hosted runner on Cloud Mac (macOS)
This page answered why the execution layer must exist. Next: labels, actions/runner, launchd, and token rotation — putting Diff and Fact on one Apple Silicon node. Then same-host scheduling for Claude Code and CI, workspace isolation, and Mac mini vs Cloud Mac cost.
