Why GitHub Runner is the execution engine of the Cloud Mac AI Stack

After Claude Code finishes editing, who builds, tests, and ships?
Series slogan: Claude Code produces Diff; GitHub Runner produces Fact.

Cloud Mac AI Stack · L1  ·  2026.06.03  ·  ~12 min read  ·  architecture piece, no runner registration steps

GitHub Actions self-hosted runner on Cloud Mac running an iOS build pipeline

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.”

L1
Execution layer (Fact)
No registration tutorial
1
iOS delivery chain diagram

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:

  1. A developer runs Claude Code over SSH on Cloud Mac. The agent replies: “All tests have passed.”
  2. They git push and head into a meeting confident.
  3. GitHub Actions fires; the workflow still has runs-on: ubuntu-latest.
  4. 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 xcodebuild and fastlane on 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:

  1. Accept repo eventson: push, pull_request, workflow_dispatch dispatch jobs to labeled self-hosted runners.
  2. Run native macOS toolchainsxcodebuild, swift test, notarytool, signing and archive; hard requirements Linux runners cannot satisfy.
  3. 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 backendsdocker build plus 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:

  1. L0 — Cloud Mac base (always-on macOS, egress, SSH).
  2. L1 — GitHub Runner so push → green/red is repeatable (this article).
  3. L2–L3Ollama, Claude Code on top of objective build results.
  4. 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.

Back to blog · full Cloud Mac AI Stack series
macOS CI Cloud Mac pricing