Skip to content

ci: add reload-build workflow for cloud reload Blacksmith builder - #6354

Merged
lawrencecchen merged 1 commit into
mainfrom
feat-blacksmith-reload-build
Jun 17, 2026
Merged

lawrencecchen merged 1 commit into
mainfrom
feat-blacksmith-reload-build

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

Adds .github/workflows/reload-build.yml, a workflow_dispatch-only build that the cmuxterm-hq cloud reload scripts use as their Blacksmith builder alternative to SSH-leasing a fleet Mac.

scripts/reload-cloud.sh --builder blacksmith and scripts/reload-cloud-ios.sh --builder blacksmith dispatch this workflow against an ephemeral branch holding the caller's working tree, then gh run download the artifact and install it locally (macOS: ad-hoc patch + install; iOS: local sign + devicectl install).

  • macOS: builds cmux DEV <tag>.app via the same scripts/reload.sh the fleet uses, zips it.
  • iOS: builds an unsigned Debug .xcarchive for dev.cmux.ios.<slug> (signed locally by the caller).
  • runs-on: ${{ inputs.runner }}, default blacksmith-6vcpu-macos-26 (all options are existing actionlint labels).
  • Emits per-phase timings (artifact timings.json + step summary) for the Blacksmith-vs-fleet comparison.

workflow_dispatch only: no push/pull_request triggers, so it never adds to the heavy CI fan-out. Committed with [skip ci] to keep this PR off the paid runners.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Low Risk
CI-only addition with read-only repo permissions and no push/PR triggers; reuses existing build scripts without changing app runtime code.

Overview
Adds a workflow_dispatch-only GitHub Actions workflow so cloud reload scripts can build tagged dev artifacts on Blacksmith (or other macOS runners) instead of leasing a fleet Mac.

Callers pass tag, optional ref, platform (macos | ios), runner, and a nonce echoed in run-name so the dispatcher can find the run. Concurrency cancels in-flight builds for the same tag+platform.

macOS runs scripts/reload.sh with the tag, zips the built app. iOS produces an unsigned Debug .xcarchive with dev.cmux.ios.<slug> (signing stays local). Shared setup: checkout, Xcode selection, zig install, GhosttyKit provisioning, CMUX_SKIP_ZIG_BUILD=1. Outputs timings.json and a step summary, then uploads a 3-day artifact.

No push/pull_request triggers—manual dispatch only, so it does not expand the main CI fan-out.

Reviewed by Cursor Bugbot for commit 4b9de05. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Adds a reload-build GitHub Actions workflow to build a tagged dev macOS app or an unsigned iOS archive on Blacksmith macOS runners for cloud reload, replacing fleet Mac SSH leases. It’s manual-only (workflow_dispatch) and uploads a ready-to-install artifact.

  • New Features

    • Adds .github/workflows/reload-build.yml with inputs: tag, ref, platform (macos|ios), runner, nonce.
    • macOS: builds “cmux DEV .app” via scripts/reload.sh and zips it as app.zip.
    • iOS: archives unsigned Debug .xcarchive for dev.cmux.ios.<slug> as archive.zip.
    • Uploads reload-<tag>-<platform> with timings.json; retention 3 days. Concurrency is per tag+platform; run name includes tag/platform/nonce.
    • Default runner blacksmith-6vcpu-macos-26 (other labels supported).
  • Migration

    • Trigger via scripts/reload-cloud.sh --builder blacksmith or scripts/reload-cloud-ios.sh --builder blacksmith.
    • Or dispatch manually with inputs, then use gh run download to fetch and install locally.

Written for commit 4b9de05. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Chores
    • Added a new manual build workflow for generating reload-build artifacts on macOS and iOS platforms, with automated timing measurement and artifact preservation policies.

…ilder

[skip ci]

reload-cloud.sh / reload-cloud-ios.sh --builder blacksmith dispatch this to
build a tagged dev macOS app or unsigned iOS archive on a Blacksmith macOS
runner and upload it for local download. workflow_dispatch only, so it never
joins the push/PR CI fan-out.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Building Building Preview, Comment Jun 17, 2026 10:40pm
cmux-staging Building Building Preview, Comment Jun 17, 2026 10:40pm

@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3cd8f8b2-ccd9-4791-bdeb-b65ffef208dd

📥 Commits

Reviewing files that changed from the base of the PR and between fceee9a and 4b9de05.

📒 Files selected for processing (1)
  • .github/workflows/reload-build.yml

📝 Walkthrough

Walkthrough

A new manually triggered GitHub Actions workflow reload-build.yml is added. It accepts inputs for tag, ref, platform (macOS or iOS), runner, and nonce; enforces per-tag+platform concurrency with cancellation; runs platform-specific build steps; records timing data; and uploads the resulting artifact directory with a 3-day retention policy.

Changes

reload-build Workflow

Layer / File(s) Summary
Workflow trigger, permissions, and concurrency
.github/workflows/reload-build.yml
Declares workflow_dispatch inputs (tag, ref, platform, runner, nonce), sets dynamic run-name, constrains permissions to contents: read, and enforces per-tag+platform concurrency with in-progress cancellation.
Job setup: runner, env, checkout, Xcode, Zig, GhosttyKit
.github/workflows/reload-build.yml
Configures the build job with runner label, 60-minute timeout, environment variables, recursive-submodule checkout, timing epoch capture, Xcode version selection, Zig installation, and conditional GhosttyKit provisioning for the macOS platform.
macOS and iOS platform build steps
.github/workflows/reload-build.yml
macOS step runs ./scripts/reload.sh, parses the produced app path from /tmp/reload.log, validates it, and zips into artifact/app.zip. iOS step normalizes the tag into a slug, ensures the iOS simulator platform is registered (downloading if absent), runs xcodebuild archive with CODE_SIGNING_ALLOWED/REQUIRED=NO, validates the .xcarchive, and zips into artifact/archive.zip.
Timing recording and artifact upload
.github/workflows/reload-build.yml
Always writes artifact/timings.json with deps/build/post-checkout epoch fields and appends a formatted timing section to $GITHUB_STEP_SUMMARY; then uploads artifact/ as reload-{tag}-{platform} with 3-day retention, hard-failing when no files are found.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 A workflow hops in, fresh and new,
Dispatch it by tag — macOS or iOS will do!
Zig installed, GhosttyKit set with care,
Archives zipped and timed beyond compare.
Three days retained, then off they fly —
The rabbit builds on, waving goodbye! 🏗️

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-blacksmith-reload-build

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@lawrencecchen
lawrencecchen merged commit 5604170 into main Jun 17, 2026
7 of 10 checks passed
@lawrencecchen
lawrencecchen deleted the feat-blacksmith-reload-build branch June 17, 2026 22:41

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4b9de05. Configure here.

if ! ios_ready; then
echo "iOS platform not registered; installing via downloadPlatform iOS"
xcodebuild -downloadPlatform iOS 2>&1 | tr '\r' '\n' | grep -ivE 'Preparing to download|registering download' | tail -8 || true
ios_ready || { echo "iOS platform still not registered; archive would fail" >&2; exit 1; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ios_ready grep never matches

High Severity

The ios_ready helper looks for iOS … (Ready) in xcrun simctl runtime list, but typical output lists runtimes like iOS 18.0 (22A3351) with a build number in parentheses, not (Ready). After xcodebuild -downloadPlatform iOS, the same check still fails and the step exits before archiving.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4b9de05. Configure here.

run: |
set -euo pipefail
slug="$(printf '%s' "$BUILD_TAG" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9-' '-' | sed 's/-\{2,\}/-/g; s/^-//; s/-$//')"
bundle_id="dev.cmux.ios.$slug"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty iOS slug breaks bundle

Medium Severity

The iOS slug sanitizer can produce an empty string when the dev tag has no alphanumeric characters, yielding bundle id dev.cmux.ios. with no suffix. ios/scripts/reload.sh’s sanitize_tag falls back to dev and rejects unusable tags; this workflow does neither before xcodebuild archive.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4b9de05. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a workflow_dispatch-only GitHub Actions workflow that builds a tagged macOS .app or unsigned iOS .xcarchive on a Blacksmith runner and uploads it as a short-lived artifact for the cloud reload scripts to download and install locally.

  • macOS build: calls reload.sh --tag $BUILD_TAG, zips the resulting .app with ditto.
  • iOS build: installs the iOS platform if absent, runs xcodebuild archive with code-signing disabled, then zips the .xcarchive.
  • Timings: always writes artifact/timings.json and a step summary for Blacksmith-vs-fleet benchmarking; the nonce input lets the dispatcher match its run by title since gh workflow run doesn't return a run ID.

Confidence Score: 3/5

The workflow is safe to land once the macOS step's direct template-expression-in-shell pattern is fixed; the iOS step already shows the correct env-var approach.

The macOS build step expands inputs.tag directly into a shell command before the shell runs it, meaning any dispatcher with repo write access can inject arbitrary shell code by supplying a crafted tag. The iOS step avoids this by binding the same value to BUILD_TAG via env: — the inconsistency makes the macOS path exploitable while the iOS path is fine. The timings heredoc has a secondary issue where unquoted expressions can produce malformed JSON, and t0 lacks the fallback that t1 has.

.github/workflows/reload-build.yml — the macOS build step and the timings heredoc need attention.

Security Review

  • Script injection (.github/workflows/reload-build.yml, line 112): ${{ inputs.tag }} is expanded by the GitHub Actions template engine directly into a run: shell command in the macOS build step. Any workflow dispatcher with repository write access can supply a tag containing shell metacharacters to inject arbitrary commands. The iOS step avoids this correctly via env: BUILD_TAG: ${{ inputs.tag }} + $BUILD_TAG in the script.

Important Files Changed

Filename Overview
.github/workflows/reload-build.yml New workflow_dispatch-only build workflow for Blacksmith runner; macOS step injects inputs.tag directly into shell (script injection risk), and the timings heredoc embeds unescaped inputs that can produce malformed JSON or arithmetic failures.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant HQ as cmuxterm-hq script
    participant GH as GitHub Actions
    participant Runner as Blacksmith Runner
    participant Caller as Local Machine

    HQ->>GH: workflow_dispatch(tag, ref, platform, runner, nonce)
    GH->>Runner: allocate runner (inputs.runner)
    Runner->>Runner: checkout ref
    Runner->>Runner: select Xcode + install zig
    alt "platform == macos"
        Runner->>Runner: provision GhosttyKit
        Runner->>Runner: reload.sh --tag BUILD_TAG
        Runner->>Runner: zip .app to artifact/app.zip
    else "platform == ios"
        Runner->>Runner: ensure iOS platform
        Runner->>Runner: ensure-ghosttykit.sh
        Runner->>Runner: xcodebuild archive (unsigned)
        Runner->>Runner: zip .xcarchive to artifact/archive.zip
    end
    Runner->>Runner: write artifact/timings.json
    Runner->>GH: upload-artifact (3 days retention)
    HQ->>GH: gh run download
    GH-->>Caller: artifact zip
    Caller->>Caller: sign + install locally
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant HQ as cmuxterm-hq script
    participant GH as GitHub Actions
    participant Runner as Blacksmith Runner
    participant Caller as Local Machine

    HQ->>GH: workflow_dispatch(tag, ref, platform, runner, nonce)
    GH->>Runner: allocate runner (inputs.runner)
    Runner->>Runner: checkout ref
    Runner->>Runner: select Xcode + install zig
    alt "platform == macos"
        Runner->>Runner: provision GhosttyKit
        Runner->>Runner: reload.sh --tag BUILD_TAG
        Runner->>Runner: zip .app to artifact/app.zip
    else "platform == ios"
        Runner->>Runner: ensure iOS platform
        Runner->>Runner: ensure-ghosttykit.sh
        Runner->>Runner: xcodebuild archive (unsigned)
        Runner->>Runner: zip .xcarchive to artifact/archive.zip
    end
    Runner->>Runner: write artifact/timings.json
    Runner->>GH: upload-artifact (3 days retention)
    HQ->>GH: gh run download
    GH-->>Caller: artifact zip
    Caller->>Caller: sign + install locally
Loading

Reviews (1): Last reviewed commit: "ci: add reload-build workflow_dispatch f..." | Re-trigger Greptile

Comment on lines +107 to +112
- name: Build tagged macOS app
if: ${{ inputs.platform == 'macos' }}
id: build_macos
run: |
set -euo pipefail
./scripts/reload.sh --tag "${{ inputs.tag }}" --swift-frontend-workaround 2>&1 | tee /tmp/reload.log

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security The macOS build step inlines ${{ inputs.tag }} directly into the shell command. GitHub Actions evaluates ${{ }} expressions before passing the string to the shell, so a tag like foo" --extra-flag or $(evil) is injected verbatim, turning the reload.sh invocation into arbitrary shell execution. The iOS step already does this correctly by binding the value to BUILD_TAG via env: and referencing $BUILD_TAG in the script — apply the same pattern here.

Suggested change
- name: Build tagged macOS app
if: ${{ inputs.platform == 'macos' }}
id: build_macos
run: |
set -euo pipefail
./scripts/reload.sh --tag "${{ inputs.tag }}" --swift-frontend-workaround 2>&1 | tee /tmp/reload.log
- name: Build tagged macOS app
if: ${{ inputs.platform == 'macos' }}
id: build_macos
env:
BUILD_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
./scripts/reload.sh --tag "$BUILD_TAG" --swift-frontend-workaround 2>&1 | tee /tmp/reload.log

Comment on lines +171 to +176
cat > artifact/timings.json <<JSON
{
"tag": "${{ inputs.tag }}",
"platform": "${{ inputs.platform }}",
"runner": "${{ inputs.runner }}",
"ref": "${{ inputs.ref }}",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The tag and ref inputs are expanded by the GitHub Actions template engine directly inside the heredoc body, before the shell runs. A tag containing a " character (e.g. v1.0"injected) will produce syntactically invalid JSON in timings.json. Since t0 has no || 0 fallback (unlike t1), an empty steps.t0.outputs.epoch — which can happen if the timer step was skipped on a cancellation path — would also cause an arithmetic error in the $(( now - t0 )) expressions.

Suggested change
cat > artifact/timings.json <<JSON
{
"tag": "${{ inputs.tag }}",
"platform": "${{ inputs.platform }}",
"runner": "${{ inputs.runner }}",
"ref": "${{ inputs.ref }}",
cat > artifact/timings.json <<JSON
{
"tag": "$TAG",
"platform": "${{ inputs.platform }}",
"runner": "${{ inputs.runner }}",
"ref": "$REF",

set -euo pipefail
mkdir -p artifact
now=$(date +%s)
t0=${{ steps.t0.outputs.epoch }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 t0 missing fallback — if the "Start timer" step is skipped (e.g. on a mid-run cancellation that still lets always() steps execute), steps.t0.outputs.epoch is empty and t0=${{ steps.t0.outputs.epoch }} expands to t0=, making every $(( … - t0 )) arithmetic expression fail. t1 already has || 0 for exactly this reason; t0 should too.

This branch was successfully deployed

1 active deployment
Preview – cmux — 4b9de057 Deployed Jun 17, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant