From eb9500d1c28d38a4c3f86b1821b798bb2ae6569e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Dec 2025 12:24:28 +0000 Subject: [PATCH 01/12] Clarify why macOS runner costs matter for iOS/macOS developers Add context that iOS/macOS developers have no choice but to use expensive macOS runners, making the cost savings value proposition more explicit. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dfe161e..1416e04 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Self-hosted GitHub Actions runners for your Mac ## Save time and money on macOS builds -GitHub charges [**$0.062/minute**](https://docs.github.com/en/billing/reference/actions-runner-pricing) for their cheapest `macos` runners. A 20-minute build costs **$1.24**. Push twice a day and you're spending **over $50/month** on CI. Run the same jobs on your MacBook and they finish in less than half the time for $0. +GitHub charges [**$0.062/minute**](https://docs.github.com/en/billing/reference/actions-runner-pricing) for their cheapest `macos` runners—and iOS/macOS developers have no choice but to use them. A 20-minute build costs **$1.24**. Push twice a day and you're spending **over $50/month** on CI. Run the same jobs on your MacBook and they finish in less than half the time for $0. Here's what some open source projects would save: From 0d01a8ac3f62ed7cb1793bbb0300d34549368768 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Dec 2025 12:26:24 +0000 Subject: [PATCH 02/12] Revert README change - keep original pricing description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1416e04..dfe161e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Self-hosted GitHub Actions runners for your Mac ## Save time and money on macOS builds -GitHub charges [**$0.062/minute**](https://docs.github.com/en/billing/reference/actions-runner-pricing) for their cheapest `macos` runners—and iOS/macOS developers have no choice but to use them. A 20-minute build costs **$1.24**. Push twice a day and you're spending **over $50/month** on CI. Run the same jobs on your MacBook and they finish in less than half the time for $0. +GitHub charges [**$0.062/minute**](https://docs.github.com/en/billing/reference/actions-runner-pricing) for their cheapest `macos` runners. A 20-minute build costs **$1.24**. Push twice a day and you're spending **over $50/month** on CI. Run the same jobs on your MacBook and they finish in less than half the time for $0. Here's what some open source projects would save: From 6aa58e7f2c9dc482dfdbd5390356d5b9b92a6d10 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Dec 2025 15:58:43 +0000 Subject: [PATCH 03/12] Add macos-15-large to XcodeBenchmark workflow Run benchmark on both macos-latest and macos-15-large (M1) to get a fairer comparison against GitHub's faster runner tier. --- .github/workflows/xcode-benchmark.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/xcode-benchmark.yaml b/.github/workflows/xcode-benchmark.yaml index 20568ec..10f4ca1 100644 --- a/.github/workflows/xcode-benchmark.yaml +++ b/.github/workflows/xcode-benchmark.yaml @@ -5,7 +5,10 @@ on: jobs: benchmark: - runs-on: macos-latest + strategy: + matrix: + runner: [macos-latest, macos-15-large] + runs-on: ${{ matrix.runner }} steps: - name: Show runner info run: | From 956f0e1b3145926e24dbe4d97fbe82749b020e46 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Dec 2025 16:45:34 +0000 Subject: [PATCH 04/12] Run XcodeBenchmark on macos-15-xlarge --- .github/workflows/xcode-benchmark.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/xcode-benchmark.yaml b/.github/workflows/xcode-benchmark.yaml index 10f4ca1..7fb3c90 100644 --- a/.github/workflows/xcode-benchmark.yaml +++ b/.github/workflows/xcode-benchmark.yaml @@ -7,7 +7,7 @@ jobs: benchmark: strategy: matrix: - runner: [macos-latest, macos-15-large] + runner: [macos-15-xlarge] runs-on: ${{ matrix.runner }} steps: - name: Show runner info From ef553acefccafdf14e1062c907e858a60da8b72b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Dec 2025 18:28:39 +0000 Subject: [PATCH 05/12] Update benchmark table with all GitHub runner tiers - Add macos-15-large (Intel x12) and macos-15-xlarge (M2 Pro x5) - Include pricing for each runner tier - Link times to actual job runs for verification - Remove redundant "vs GitHub" column --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dfe161e..be12049 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,13 @@ Here's what some open source projects would save: Local builds are also faster. Based on [XcodeBenchmark](https://github.com/devMEremenko/XcodeBenchmark): -| Runner | Time | vs GitHub | -|--------|------|-----------| -| GitHub macos-latest | [967s](https://github.com/bfulton/localmost/actions/runs/20388833445/job/58594848226) | — | -| MacBook Air M2 (2022) | 202s | **4.8x faster** | -| MacBook Pro M4 Max (2024) | 77s | **12.6x faster** | +| Runner | Time | +|--------|------| +| GitHub macos-latest M1 x3 ($0.06/m) | [838s](https://github.com/bfulton/localmost/actions/runs/20525324038/job/58967518701) | +| GitHub macos-15-large Intel x12 ($0.08/m) | [955s](https://github.com/bfulton/localmost/actions/runs/20525324038/job/58967518696) | +| GitHub macos-15-xlarge M2 Pro x5 ($0.10/m) | [339s](https://github.com/bfulton/localmost/actions/runs/20525919481/job/58969135831) | +| MacBook Air M2 x8 (2022) | 202s | +| MacBook Pro M4 Max x16 (2024) | 77s | ## Why else use localmost? From 84525ef910e75c1c0a21ee9f0682e7969dc22e4d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Dec 2025 18:55:17 +0000 Subject: [PATCH 06/12] Add security note and trusted authors roadmap item - Add visible security warning in "What It Is" section linking to SECURITY.md - Add "Trusted authors for public repos" to roadmap with detailed feature spec --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index be12049..b38737b 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ Features: localmost is a macOS app that manages GitHub's official [actions-runner](https://github.com/actions/runner) binary. It handles authentication, registration, runner process lifecycle, and automatic fallback — the tedious parts of self-hosted runners. +**Security note:** Running CI jobs on your local machine has inherent risks—especially for public repos that accept external contributions. localmost sandboxes runner processes and restricts network access, but these are not VM-level isolation. See [SECURITY.md](SECURITY.md) for details on the threat model and recommendations. + ## Architecture localmost architecture diagram @@ -217,6 +219,7 @@ npm run make Future feature ideas: +- **Trusted authors for public repos** - Control who can trigger builds on your machine. Options: never build public repos, only build from trusted authors (default: you + known bots, customizable), or always build (with high-friction confirmation). Jobs from untrusted authors fail with a clear error. - **Quick actions** - Re-run failed job, cancel all jobs. - **Audit logging** - Detailed logs of what each job accessed. - **Network policy customization** - User-defined network allowlists per repo. From 530c10f37ccfd0d4c50137a3f428d0c9adb26ddb Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Dec 2025 18:57:30 +0000 Subject: [PATCH 07/12] Fix trusted contributors description - check all repo contributors, not just PR author --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b38737b..22a1ead 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ npm run make Future feature ideas: -- **Trusted authors for public repos** - Control who can trigger builds on your machine. Options: never build public repos, only build from trusted authors (default: you + known bots, customizable), or always build (with high-friction confirmation). Jobs from untrusted authors fail with a clear error. +- **Trusted contributors for public repos** - Control which repos can run on your machine based on their contributor list. Options: never build public repos, only build repos where all contributors are trusted (default: you + known bots, customizable), or always build (with high-friction confirmation). Repos with untrusted contributors fail with a clear error. - **Quick actions** - Re-run failed job, cancel all jobs. - **Audit logging** - Detailed logs of what each job accessed. - **Network policy customization** - User-defined network allowlists per repo. From f7224f790e389c9d779b2eb8694dcb2a89ba8315 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Dec 2025 19:03:13 +0000 Subject: [PATCH 08/12] Add Linux and Windows host support to roadmap --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 22a1ead..9b7ccb8 100644 --- a/README.md +++ b/README.md @@ -229,6 +229,7 @@ Future feature ideas: - **Disk space monitoring** - Warn or pause when disk is low, auto-clean old work dirs. - **Runner handoff** - Transfer a running job to GitHub-hosted if you need to leave. - **Reactive state management** - Unify disk state, React state, and state machine into a single reactive store to prevent synchronization bugs. +- **Linux and Windows host support** - Run self-hosted runners on non-Mac machines for projects that need them. Bugs and quick improvements: From 72622420034942ff67f595840a4c3bc2769550fe Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Dec 2025 19:17:45 +0000 Subject: [PATCH 09/12] Add graceful heartbeat shutdown to roadmap --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9b7ccb8..e3272e2 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,7 @@ npm run make Future feature ideas: - **Trusted contributors for public repos** - Control which repos can run on your machine based on their contributor list. Options: never build public repos, only build repos where all contributors are trusted (default: you + known bots, customizable), or always build (with high-friction confirmation). Repos with untrusted contributors fail with a clear error. +- **Graceful heartbeat shutdown** - On clean exit, immediately mark heartbeat stale so workflows fall back to cloud without waiting for the 90s timeout. - **Quick actions** - Re-run failed job, cancel all jobs. - **Audit logging** - Detailed logs of what each job accessed. - **Network policy customization** - User-defined network allowlists per repo. From b6d88aa2b4686af616e4793eccb047cf7c547924 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Dec 2025 19:22:43 +0000 Subject: [PATCH 10/12] Cap runner parallelism at 8, add roadmap item for higher cap Due to proxy registration limitations, cap max concurrent jobs at 8 instead of 16. Added roadmap item to improve broker connection handling to support higher parallelism in the future. Updated: - MAX_RUNNER_COUNT constant - Config comments - Target manager clamping - Settings UI slider - README features and architecture sections - Test expectations --- README.md | 5 +++-- src/main/config.ts | 4 ++-- src/main/target-manager.test.ts | 4 ++-- src/main/target-manager.ts | 2 +- src/renderer/components/SettingsPage.tsx | 2 +- src/shared/constants.ts | 2 +- src/shared/types.ts | 2 +- 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e3272e2..43ddaa3 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Features: - **Automatic fallback** — workflows detect when your Mac is available; fall back to hosted runners when it's not - **One-click setup** — no terminal commands, no manually generating registration tokens - **Lid-close protection** — close your laptop without killing in-progress jobs -- **Multi-runner parallelism** — run 1-16 concurrent jobs +- **Multi-runner parallelism** — run 1-8 concurrent jobs - **Network isolation** — runner traffic is proxied through an allowlist (GitHub, npm, PyPI, etc.) - **Filesystem sandboxing** — runner processes can only write to their working directory - **Resource-aware scheduling** — automatically pause runners when on battery or during video calls @@ -57,7 +57,7 @@ localmost is a macOS app that manages GitHub's official [actions-runner](https:/ localmost architecture diagram - **Runner proxy** — maintains long-poll sessions with GitHub's broker to receive job assignments -- **Runner pool** — 1-16 worker instances that execute jobs in sandboxed environments +- **Runner pool** — 1-8 worker instances that execute jobs in sandboxed environments - **HTTP proxy** — allowlist-based network isolation for runner traffic (GitHub, npm, PyPI, etc.) - **Build cache** — persistent tool cache shared across job runs (Node.js, Python, etc.) @@ -231,6 +231,7 @@ Future feature ideas: - **Runner handoff** - Transfer a running job to GitHub-hosted if you need to leave. - **Reactive state management** - Unify disk state, React state, and state machine into a single reactive store to prevent synchronization bugs. - **Linux and Windows host support** - Run self-hosted runners on non-Mac machines for projects that need them. +- **Higher parallelism cap** - Improve proxy registration to support 16+ concurrent runners (currently capped at 8 due to broker connection limits). Bugs and quick improvements: diff --git a/src/main/config.ts b/src/main/config.ts index f24009b..0abb117 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -50,7 +50,7 @@ export interface AppConfig { orgName?: string; runnerName?: string; labels?: string; - runnerCount?: number; // Number of parallel runners (1-16) + runnerCount?: number; // Number of parallel runners (1-8) }; theme?: string; launchAtLogin?: boolean; @@ -62,7 +62,7 @@ export interface AppConfig { userFilter?: UserFilterConfig; /** Multi-target configuration - list of repos/orgs to register runners for */ targets?: Target[]; - /** Maximum concurrent jobs across all targets (1-16, defaults to 4) */ + /** Maximum concurrent jobs across all targets (1-8, defaults to 4) */ maxConcurrentJobs?: number; /** Power settings (battery/video call pausing) */ power?: PowerConfig; diff --git a/src/main/target-manager.test.ts b/src/main/target-manager.test.ts index fc6fc85..eaf093a 100644 --- a/src/main/target-manager.test.ts +++ b/src/main/target-manager.test.ts @@ -307,10 +307,10 @@ describe('TargetManager', () => { expect(mockSaveConfig).toHaveBeenCalledWith({ maxConcurrentJobs: 1 }); }); - it('should clamp value to maximum of 16', () => { + it('should clamp value to maximum of 8', () => { mockLoadConfig.mockReturnValue({}); manager.setMaxConcurrentJobs(100); - expect(mockSaveConfig).toHaveBeenCalledWith({ maxConcurrentJobs: 16 }); + expect(mockSaveConfig).toHaveBeenCalledWith({ maxConcurrentJobs: 8 }); }); }); diff --git a/src/main/target-manager.ts b/src/main/target-manager.ts index 8bbaced..93f3a19 100644 --- a/src/main/target-manager.ts +++ b/src/main/target-manager.ts @@ -213,7 +213,7 @@ export class TargetManager { const log = () => getLogger(); const config = loadConfig(); const oldValue = config.maxConcurrentJobs ?? 4; - const newValue = Math.max(1, Math.min(16, count)); + const newValue = Math.max(1, Math.min(8, count)); if (oldValue !== newValue) { log()?.info(`[Settings] maxConcurrentJobs: ${oldValue} -> ${newValue}`); } diff --git a/src/renderer/components/SettingsPage.tsx b/src/renderer/components/SettingsPage.tsx index 41cbd2d..b411fb2 100644 --- a/src/renderer/components/SettingsPage.tsx +++ b/src/renderer/components/SettingsPage.tsx @@ -364,7 +364,7 @@ const SettingsPage: React.FC = ({ onBack, scrollToSection, on updateRunnerConfig({ runnerCount: parseInt(e.target.value, 10) })} /> diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 03515d6..f1cb1b9 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -90,7 +90,7 @@ export const DEFAULT_RUNNER_COUNT = 4; export const MIN_RUNNER_COUNT = 1; /** Maximum number of runner instances */ -export const MAX_RUNNER_COUNT = 16; +export const MAX_RUNNER_COUNT = 8; /** Default max job history entries to keep */ export const DEFAULT_MAX_JOB_HISTORY = 10; diff --git a/src/shared/types.ts b/src/shared/types.ts index 8dac09b..19e11b6 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -246,7 +246,7 @@ export interface ConfigureOptions { orgName?: string; // Required for org level runnerName: string; labels: string[]; - runnerCount?: number; // Number of parallel runners (1-16), defaults to 1 + runnerCount?: number; // Number of parallel runners (1-8), defaults to 1 } export type SleepProtection = 'never' | 'when-busy' | 'always'; From 49aff73c5cf9521c39a75ab0f1948f4ae158d8b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Dec 2025 19:23:34 +0000 Subject: [PATCH 11/12] Fix parallelism cap reason - serial registration time, not connection limits --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 43ddaa3..b191a6f 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ Future feature ideas: - **Runner handoff** - Transfer a running job to GitHub-hosted if you need to leave. - **Reactive state management** - Unify disk state, React state, and state machine into a single reactive store to prevent synchronization bugs. - **Linux and Windows host support** - Run self-hosted runners on non-Mac machines for projects that need them. -- **Higher parallelism cap** - Improve proxy registration to support 16+ concurrent runners (currently capped at 8 due to broker connection limits). +- **Higher parallelism cap** - Parallelize proxy registration to support 16+ concurrent runners (currently capped at 8 due to serial registration time). Bugs and quick improvements: From 2094a47ec7520a9850da8c7f3e65a8420ed748a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Dec 2025 19:25:33 +0000 Subject: [PATCH 12/12] Add ephemeral VM isolation to roadmap --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b191a6f..9f9362b 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,7 @@ Future feature ideas: - **Reactive state management** - Unify disk state, React state, and state machine into a single reactive store to prevent synchronization bugs. - **Linux and Windows host support** - Run self-hosted runners on non-Mac machines for projects that need them. - **Higher parallelism cap** - Parallelize proxy registration to support 16+ concurrent runners (currently capped at 8 due to serial registration time). +- **Ephemeral VM isolation** - Run each job in a fresh lightweight VM for stronger isolation between jobs. Bugs and quick improvements: