Skip to content

Publish separate universal nightly track - #1067

Merged
lawrencecchen merged 8 commits into
mainfrom
task-universal-macos-build
Mar 8, 2026
Merged

lawrencecchen merged 8 commits into
mainfrom
task-universal-macos-build

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Mar 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • keep the existing Apple Silicon nightly as the default com.cmuxterm.app.nightly track with appcast.xml
  • add a second universal nightly track with bundle ID com.cmuxterm.app.nightly.universal and appcast-universal.xml
  • publish both DMGs and both appcasts in the nightly release while keeping branch-dispatch runs safe

Testing

  • bash tests/test_nightly_universal_build.sh

Issues

  • Task: publish separate Apple Silicon and universal nightly channels without changing stable release packaging

Summary by CodeRabbit

  • Chores
    • Nightly now produces and publishes separate Apple Silicon (arm64) and Universal macOS artifacts with distinct update tracks, appcasts, notarized DMGs, and immutable naming; publishing of official nightly releases is gated to main while branch artifacts are uploaded for other refs.
    • Improved appcast generation, signature fallback, and packaging to ensure reliable signed update feeds and per-variant notarization.
  • Tests
    • Added regression tests validating dual-variant builds, binary architecture verification, release settings, artifact uploads, and publish-vs-branch gating.

@vercel

vercel Bot commented Mar 8, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Mar 8, 2026 0:14am

@coderabbitai

coderabbitai Bot commented Mar 8, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Publish separate universal nightly track' clearly and concisely summarizes the main change: introducing a second universal nightly build track alongside the existing Apple Silicon track.
Description check ✅ Passed The PR description covers the required template sections: a clear Summary explaining both nightly tracks, Testing instructions, and Issues/context. All core information is present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch task-universal-macos-build

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

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/test_nightly_universal_build.sh">

<violation number="1" location="tests/test_nightly_universal_build.sh:8">
P1: This entire test file verifies source-code text patterns (awk/grep on the YAML workflow file) rather than observable runtime behavior, which the project's test quality policy explicitly prohibits.

If the goal is to lock in universal-build and publish-guard behavior, consider a test that actually exercises the workflow outputs — e.g., a small script that invokes the build with the expected flags and verifies the resulting binary is universal via `lipo -archs`, or a unit test for the publish-decision logic.</violation>
</file>

<file name=".github/workflows/nightly.yml">

<violation number="1" location=".github/workflows/nightly.yml:42">
P2: The fallback to `'main'` when `context.ref` has an unrecognized prefix is an unsafe default since `isMainRef = true` gates real publishing. If an unexpected ref format reaches this code, it would silently publish to the production nightly release. Consider defaulting to a non-main value so the workflow fails safe (build-only, upload artifacts) rather than fail dangerous (publish).</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@@ -0,0 +1,67 @@
#!/usr/bin/env bash

@cubic-dev-ai cubic-dev-ai Bot Mar 8, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: This entire test file verifies source-code text patterns (awk/grep on the YAML workflow file) rather than observable runtime behavior, which the project's test quality policy explicitly prohibits.

If the goal is to lock in universal-build and publish-guard behavior, consider a test that actually exercises the workflow outputs — e.g., a small script that invokes the build with the expected flags and verifies the resulting binary is universal via lipo -archs, or a unit test for the publish-decision logic.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test_nightly_universal_build.sh, line 8:

<comment>This entire test file verifies source-code text patterns (awk/grep on the YAML workflow file) rather than observable runtime behavior, which the project's test quality policy explicitly prohibits.

If the goal is to lock in universal-build and publish-guard behavior, consider a test that actually exercises the workflow outputs — e.g., a small script that invokes the build with the expected flags and verifies the resulting binary is universal via `lipo -archs`, or a unit test for the publish-decision logic.</comment>

<file context>
@@ -0,0 +1,67 @@
+ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
+WORKFLOW_FILE="$ROOT_DIR/.github/workflows/nightly.yml"
+
+if ! awk '
+  /^      - name: Build app \(Release\)/ { in_build=1; next }
+  in_build && /^      - name:/ { in_build=0 }
</file context>
Fix with Cubic

Comment on lines +42 to +44
const requestedRef = context.ref.startsWith('refs/heads/')
? context.ref.replace('refs/heads/', '')
: 'main';

@cubic-dev-ai cubic-dev-ai Bot Mar 8, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The fallback to 'main' when context.ref has an unrecognized prefix is an unsafe default since isMainRef = true gates real publishing. If an unexpected ref format reaches this code, it would silently publish to the production nightly release. Consider defaulting to a non-main value so the workflow fails safe (build-only, upload artifacts) rather than fail dangerous (publish).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/nightly.yml, line 42:

<comment>The fallback to `'main'` when `context.ref` has an unrecognized prefix is an unsafe default since `isMainRef = true` gates real publishing. If an unexpected ref format reaches this code, it would silently publish to the production nightly release. Consider defaulting to a non-main value so the workflow fails safe (build-only, upload artifacts) rather than fail dangerous (publish).</comment>

<file context>
@@ -38,46 +39,58 @@ jobs:
-            let nightlySha = null;
-            try {
-              const ref = await github.rest.git.getRef({
+            const requestedRef = context.ref.startsWith('refs/heads/')
+              ? context.ref.replace('refs/heads/', '')
+              : 'main';
</file context>
Suggested change
const requestedRef = context.ref.startsWith('refs/heads/')
? context.ref.replace('refs/heads/', '')
: 'main';
const requestedRef = context.ref.startsWith('refs/heads/')
? context.ref.replace('refs/heads/', '')
: context.ref;
Fix with Cubic

@greptile-apps

greptile-apps Bot commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR updates the nightly CI workflow to build universal (arm64 + x86_64) macOS binaries on the standard macos-15 GitHub runner and separates the "publish to GitHub Releases" path from "branch test runs" that only upload workflow artifacts.

Key changes:

  • xcodebuild now passes -destination 'generic/platform=macOS', ARCHS="arm64 x86_64", and ONLY_ACTIVE_ARCH=NO to produce fat binaries; a dedicated lipo -archs step verifies both slices post-build
  • A should_publish output is introduced in the decide job, set to 'true' only when the triggered ref resolves to main; the nightly tag move and GitHub Release publish are gated on this flag
  • Branch-dispatched runs (non-main) upload the DMG and appcast.xml as workflow artifacts instead of touching the official nightly release
  • The concurrency group is scoped to nightly-build-${{ github.ref_name }} so branch runs no longer cancel in-flight main builds

Concern: The requestedRef derivation on lines 42-44 falls back to 'main' for any non-branch ref (e.g. a tag dispatched via the API), which would set isMainRef=true and unexpectedly trigger publishing. This should be fixed to explicitly reject non-branch refs or keep the raw ref, allowing non-main refs to correctly gate the publish output.

Confidence Score: 2/5

  • Contains a logic bug where non-branch refs (e.g., tags dispatched via the GitHub API) incorrectly fall back to main, potentially triggering unintended nightly publishes.
  • The core universal-binary machinery and branch/main publish gating are sound. However, the requestedRef fallback creates a security/correctness issue: if the workflow is dispatched on a tag ref via the GitHub API (which is possible and not blocked), the code will treat it as main and trigger an unintended nightly publish. This is a concrete bug in the decide logic, not edge-case speculation. The fix is straightforward (reject non-branch refs or keep the raw ref), but the issue must be resolved before merging.
  • .github/workflows/nightly.yml — the requestedRef fallback logic (lines 42-44) must be fixed to prevent tag-based API dispatch from triggering unintended publishing.

Last reviewed commit: 1ed4aa1

Comment on lines +42 to +45
const requestedRef = context.ref.startsWith('refs/heads/')
? context.ref.replace('refs/heads/', '')
: 'main';
const isMainRef = requestedRef === 'main';

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.

Fallback to 'main' allows unintended publish for tag-dispatched workflows

When context.ref does not start with refs/heads/ (e.g., a workflow dispatch via the GitHub API on a tag reference like refs/tags/some-tag), the code falls back to requestedRef = 'main'. This sets isMainRef = true, which causes:

  1. headSha to be fetched from the current main branch tip (not from the tag's commit)
  2. should_publish to be set to 'true'
  3. The nightly tag to be moved and release assets to be published—all unintentionally

A safer approach would be to fail explicitly or to keep the raw ref, allowing isMainRef to be false for non-branch refs:

Suggested change
const requestedRef = context.ref.startsWith('refs/heads/')
? context.ref.replace('refs/heads/', '')
: 'main';
const isMainRef = requestedRef === 'main';
const requestedRef = context.ref.startsWith('refs/heads/')
? context.ref.replace('refs/heads/', '')
: null;
const isMainRef = requestedRef === 'main';

Then add a guard before processing:

Suggested change
const requestedRef = context.ref.startsWith('refs/heads/')
? context.ref.replace('refs/heads/', '')
: 'main';
const isMainRef = requestedRef === 'main';
if (context.ref.startsWith('refs/heads/') === false && context.eventName === 'workflow_dispatch') {
throw new Error('workflow_dispatch must be triggered on a branch ref, not a tag');
}

This prevents accidental or automated tag-based API dispatch from triggering an unintended nightly publish.

@lawrencecchen lawrencecchen changed the title Build universal nightly app on GitHub runner Publish separate universal nightly track Mar 8, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e23eb285cd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +8 to +12
if ! awk '
/^ - name: Build Apple Silicon app \(Release\)/ { in_arm=1; next }
/^ - name: Build universal app \(Release\)/ { in_universal=1; next }
in_arm && /^ - name:/ { in_arm=0 }
in_universal && /^ - name:/ { in_universal=0 }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Replace grep-style workflow assertions with executable checks

This test only inspects .github/workflows/nightly.yml text via awk/grep patterns, so it can pass even when the workflow behavior is broken (or fail on harmless refactors), which gives brittle, non-runtime coverage for a release pipeline change. The repository policy in /workspace/cmux/AGENTS.md explicitly forbids this pattern (“Do not add tests that only verify source code text...”), so this should be rewritten to validate observable behavior (e.g., by exercising the workflow logic through an executable seam) rather than matching YAML strings.

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/test_nightly_universal_build.sh">

<violation number="1" location="tests/test_nightly_universal_build.sh:64">
P2: The branch-upload regression check omits `appcast.xml`, so it can falsely pass when only the universal appcast is uploaded.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

in_upload && /cmux-nightly-macos\*\.dmg/ { saw_arm_artifacts=1 }
in_upload && /cmux-nightly-universal-macos\*\.dmg/ { saw_universal_artifacts=1 }
in_upload && /appcast-universal\.xml/ { saw_universal_appcast=1 }
END { exit !(saw_if && saw_upload && saw_arm_artifacts && saw_universal_artifacts && saw_universal_appcast) }

@cubic-dev-ai cubic-dev-ai Bot Mar 8, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The branch-upload regression check omits appcast.xml, so it can falsely pass when only the universal appcast is uploaded.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test_nightly_universal_build.sh, line 64:

<comment>The branch-upload regression check omits `appcast.xml`, so it can falsely pass when only the universal appcast is uploaded.</comment>

<file context>
@@ -38,9 +58,12 @@ if ! awk '
+  in_upload && /cmux-nightly-macos\*\.dmg/ { saw_arm_artifacts=1 }
+  in_upload && /cmux-nightly-universal-macos\*\.dmg/ { saw_universal_artifacts=1 }
+  in_upload && /appcast-universal\.xml/ { saw_universal_appcast=1 }
+  END { exit !(saw_if && saw_upload && saw_arm_artifacts && saw_universal_artifacts && saw_universal_appcast) }
 ' "$WORKFLOW_FILE"; then
-  echo "FAIL: non-main nightly runs must upload artifacts instead of publishing the official nightly release"
</file context>
Fix with Cubic

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/nightly.yml:
- Around line 207-224: The prepare_variant() step currently always stamps
production NIGHTLY values into Info.plist (CFBundleIdentifier and SUFeedURL)
which causes branch artifacts to appear as the real nightly; change
prepare_variant() to detect branch builds and either (A) remove Sparkle keys
(SUPublicEDKey, SUFeedURL) for non-main branches, or (B) append a short
branch/tag suffix to the CFBundleIdentifier and SUFeedURL (use the provided
tag/branch name) so bundle IDs and feed URLs are branch-scoped; apply the same
change to the equivalent block referenced for lines 227-234; reference
CFBundleIdentifier, SUFeedURL, SUPublicEDKey, and the prepare_variant() mv
rename when implementing the branch/tag logic.
- Around line 42-55: The code currently coerces any non-refs/heads/* ref to
'main'; instead, preserve tag refs and only special-case branch refs: compute
requestedRef by stripping the appropriate prefix (refs/heads/ or refs/tags/)
from context.ref rather than defaulting to 'main', determine isMainRef only when
the original ref started with refs/heads/ and the stripped name equals 'main',
and only call github.rest.repos.getBranch to override headSha when isMainRef is
true; update the logic around requestedRef, isMainRef, headSha, context.ref, and
github.rest.repos.getBranch accordingly so tag-dispatched runs keep their
requested ref and sha.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 89940e70-2a6f-4105-8abe-f9e9fb7724d8

📥 Commits

Reviewing files that changed from the base of the PR and between 1ed4aa1 and e23eb28.

📒 Files selected for processing (2)
  • .github/workflows/nightly.yml
  • tests/test_nightly_universal_build.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_nightly_universal_build.sh

Comment on lines +42 to +55
const requestedRef = context.ref.startsWith('refs/heads/')
? context.ref.replace('refs/heads/', '')
: 'main';
const isMainRef = requestedRef === 'main';

let headSha = context.sha;
if (isMainRef) {
const branch = await github.rest.repos.getBranch({
owner,
repo,
ref: 'tags/nightly',
branch: 'main',
});
if (ref.data.object.type === 'commit') {
nightlySha = ref.data.object.sha;
} else if (ref.data.object.type === 'tag') {
const tagObject = await github.rest.git.getTag({
headSha = branch.data.commit.sha;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/nightly.yml | head -100

Repository: manaflow-ai/cmux

Length of output: 4279


🌐 Web query:

GitHub Actions workflow_dispatch github.ref github.ref_type

💡 Result:

For a workflow run triggered by workflow_dispatch:

  • github.ref is the fully-qualified git ref of the branch or tag that the run is targeting, e.g.:

    • branch: refs/heads/main
    • tag: refs/tags/v1.2.3
      This is “the fully-formed ref of the branch or tag that triggered the workflow run.” [1]
  • github.ref_type tells you what kind of ref it is: branch or tag. [1]

How workflow_dispatch decides the ref:

  • From the GitHub UI (“Run workflow” button): you can choose the branch to run on, and that choice becomes github.ref / github.ref_type. [3]
  • From the REST API (Create a workflow dispatch event): you must send a ref, and GitHub documents it as “the git reference for the workflow” and says it “can be a branch or tag name.” That ref becomes github.ref / github.ref_type for the run. [2]

Example usage

- run: |
    echo "ref      = ${{ github.ref }}"
    echo "ref_type = ${{ github.ref_type }}"

Sources

  1. GitHub Docs – Contexts (github.ref, github.ref_type). [1]
  2. GitHub Docs – REST API: “Create a workflow dispatch event” (ref can be branch or tag). [2]
  3. GitHub Changelog – workflow_dispatch UI lets you choose which branch to run on. [3]

Don't coerce non-branch dispatches to main.

workflow_dispatch runs keep the branch or tag that received the dispatch, and GitHub exposes the triggering ref type via the refs/heads/ vs. refs/tags/ prefix in github.ref. With the current fallback, any non-refs/heads/* dispatch is treated as main, so a tag-dispatched run would rebuild the tip of main and set should_publish=true instead of staying on the requested ref.

🛠️ Suggested fix
-            const requestedRef = context.ref.startsWith('refs/heads/')
-              ? context.ref.replace('refs/heads/', '')
-              : 'main';
-            const isMainRef = requestedRef === 'main';
+            const requestedRef = context.ref.replace(/^refs\/(?:heads|tags)\//, '');
+            const isBranchRef = context.ref.startsWith('refs/heads/');
+            const isMainRef = isBranchRef && requestedRef === 'main';

Also applies to: 80-84

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/nightly.yml around lines 42 - 55, The code currently
coerces any non-refs/heads/* ref to 'main'; instead, preserve tag refs and only
special-case branch refs: compute requestedRef by stripping the appropriate
prefix (refs/heads/ or refs/tags/) from context.ref rather than defaulting to
'main', determine isMainRef only when the original ref started with refs/heads/
and the stripped name equals 'main', and only call github.rest.repos.getBranch
to override headSha when isMainRef is true; update the logic around
requestedRef, isMainRef, headSha, context.ref, and github.rest.repos.getBranch
accordingly so tag-dispatched runs keep their requested ref and sha.

Comment on lines +207 to +224
prepare_variant() {
local app_dir="$1"
local bundle_id="$2"
local feed_url="$3"
local app_plist="$app_dir/cmux.app/Contents/Info.plist"

/usr/libexec/PlistBuddy -c "Set :CFBundleName cmux NIGHTLY" "$app_plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName cmux NIGHTLY" "$app_plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier ${bundle_id}" "$app_plist"
/usr/libexec/PlistBuddy -c "Delete :SUPublicEDKey" "$app_plist" >/dev/null 2>&1 || true
/usr/libexec/PlistBuddy -c "Delete :SUFeedURL" "$app_plist" >/dev/null 2>&1 || true
/usr/libexec/PlistBuddy -c "Add :SUPublicEDKey string ${SPARKLE_PUBLIC_KEY}" "$app_plist"
/usr/libexec/PlistBuddy -c "Add :SUFeedURL string ${feed_url}" "$app_plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${BASE_MARKETING}-nightly.${NIGHTLY_DATE}" "$app_plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${NIGHTLY_BUILD}" "$app_plist"
/usr/libexec/PlistBuddy -c "Delete :CMUXCommit" "$app_plist" >/dev/null 2>&1 || true
/usr/libexec/PlistBuddy -c "Add :CMUXCommit string ${SHORT_SHA}" "$app_plist"
mv "$app_dir/cmux.app" "$app_dir/cmux NIGHTLY.app"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Branch-only artifacts still masquerade as the real NIGHTLY app.

These calls always stamp the production nightly bundle IDs and official feed URLs before the later artifact/publish split happens. So a non-main artifact still carries the real NIGHTLY identity on disk, which makes QA installs collide with the published nightly track instead of staying isolated. Branch runs should either remove Sparkle metadata or use a branch-scoped bundle ID/feed suffix first.

Based on learnings: For parallel/isolated builds, use --tag with a short descriptive name to create isolated apps with separate bundle IDs, sockets, and derived data paths.

Also applies to: 227-234

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/nightly.yml around lines 207 - 224, The prepare_variant()
step currently always stamps production NIGHTLY values into Info.plist
(CFBundleIdentifier and SUFeedURL) which causes branch artifacts to appear as
the real nightly; change prepare_variant() to detect branch builds and either
(A) remove Sparkle keys (SUPublicEDKey, SUFeedURL) for non-main branches, or (B)
append a short branch/tag suffix to the CFBundleIdentifier and SUFeedURL (use
the provided tag/branch name) so bundle IDs and feed URLs are branch-scoped;
apply the same change to the equivalent block referenced for lines 227-234;
reference CFBundleIdentifier, SUFeedURL, SUPublicEDKey, and the
prepare_variant() mv rename when implementing the branch/tag logic.

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/test_ci_universal_release_settings.sh">

<violation number="1" location="tests/test_ci_universal_release_settings.sh:12">
P2: This regression test relies on grep/awk checks of source text instead of verifying executable behavior, which violates the repository’s test quality policy.</violation>
</file>

<file name="scripts/setup.sh">

<violation number="1" location="scripts/setup.sh:61">
P2: The cache key/stamp does not include xcframework target, so existing same-SHA artifacts can bypass this new universal build setting and keep serving stale architecture output.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

"$ROOT_DIR/scripts/setup.sh" \
"$ROOT_DIR/scripts/build-sign-upload.sh"
do
if ! grep -Fq -- '-Dxcframework-target=universal' "$file"; then

@cubic-dev-ai cubic-dev-ai Bot Mar 8, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: This regression test relies on grep/awk checks of source text instead of verifying executable behavior, which violates the repository’s test quality policy.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test_ci_universal_release_settings.sh, line 12:

<comment>This regression test relies on grep/awk checks of source text instead of verifying executable behavior, which violates the repository’s test quality policy.</comment>

<file context>
@@ -0,0 +1,29 @@
+  "$ROOT_DIR/scripts/setup.sh" \
+  "$ROOT_DIR/scripts/build-sign-upload.sh"
+do
+  if ! grep -Fq -- '-Dxcframework-target=universal' "$file"; then
+    echo "FAIL: $file must build GhosttyKit with -Dxcframework-target=universal"
+    exit 1
</file context>
Fix with Cubic

Comment thread scripts/setup.sh
(
cd ghostty
zig build -Demit-xcframework=true -Doptimize=ReleaseFast
zig build -Demit-xcframework=true -Dxcframework-target=universal -Doptimize=ReleaseFast

@cubic-dev-ai cubic-dev-ai Bot Mar 8, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The cache key/stamp does not include xcframework target, so existing same-SHA artifacts can bypass this new universal build setting and keep serving stale architecture output.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/setup.sh, line 61:

<comment>The cache key/stamp does not include xcframework target, so existing same-SHA artifacts can bypass this new universal build setting and keep serving stale architecture output.</comment>

<file context>
@@ -58,7 +58,7 @@ else
         (
             cd ghostty
-            zig build -Demit-xcframework=true -Doptimize=ReleaseFast
+            zig build -Demit-xcframework=true -Dxcframework-target=universal -Doptimize=ReleaseFast
         )
         # Stamp the build output with the SHA it was built from
</file context>
Fix with Cubic

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 76f53aaf90

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +42 to +44
const requestedRef = context.ref.startsWith('refs/heads/')
? context.ref.replace('refs/heads/', '')
: 'main';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve dispatched ref when deciding publish eligibility

The decide script coerces any ref that is not refs/heads/* to 'main', which makes isMainRef true and enables publish/tag mutation steps for non-branch dispatches; in those cases the workflow can unexpectedly publish nightly artifacts even though the requested ref was not main. Use the actual context.ref (including tag refs) and only set should_publish for an exact refs/heads/main match.

Useful? React with 👍 / 👎.

"$ROOT_DIR/scripts/setup.sh" \
"$ROOT_DIR/scripts/build-sign-upload.sh"
do
if ! grep -Fq -- '-Dxcframework-target=universal' "$file"; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Replace source-text assertions with executable regression checks

This test validates behavior by grepping/awking repository files instead of exercising runtime build behavior, so it is brittle to refactors and can still pass when the actual pipeline/build output is wrong. The repository policy in /workspace/cmux/AGENTS.md explicitly forbids source-text-only tests, so this should be rewritten to assert observable behavior through an executable path.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
.github/workflows/nightly.yml (1)

187-190: Consider adding descriptive error messages to architecture assertions.

The bare [[ ... ]] assertions will fail silently without context. Adding explicit error messages would aid debugging in CI.

♻️ Suggested improvement
-          [[ "$ARM_APP_ARCHS" == "arm64" ]]
-          [[ "$ARM_CLI_ARCHS" == "arm64" ]]
-          [[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]]
-          [[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]]
+          [[ "$ARM_APP_ARCHS" == "arm64" ]] || { echo "ARM app expected arm64, got: $ARM_APP_ARCHS" >&2; exit 1; }
+          [[ "$ARM_CLI_ARCHS" == "arm64" ]] || { echo "ARM CLI expected arm64, got: $ARM_CLI_ARCHS" >&2; exit 1; }
+          [[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]] || { echo "Universal app expected arm64+x86_64, got: $APP_ARCHS" >&2; exit 1; }
+          [[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]] || { echo "Universal CLI expected arm64+x86_64, got: $CLI_ARCHS" >&2; exit 1; }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/nightly.yml around lines 187 - 190, The architecture
assertions using bare test expressions like [[ "$ARM_APP_ARCHS" == "arm64" ]]
and [[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]] should produce
descriptive errors on failure; update each test to log a clear message and fail
explicitly (for example replace the bare [[ ... ]] with a conditional that on
false echoes a contextual error including the variable and expected value and
then exits non‑zero, or call a small helper/assert function that does this) so
CI shows which arch check failed and why.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/nightly.yml:
- Around line 187-190: The architecture assertions using bare test expressions
like [[ "$ARM_APP_ARCHS" == "arm64" ]] and [[ "$APP_ARCHS" == *arm64* &&
"$APP_ARCHS" == *x86_64* ]] should produce descriptive errors on failure; update
each test to log a clear message and fail explicitly (for example replace the
bare [[ ... ]] with a conditional that on false echoes a contextual error
including the variable and expected value and then exits non‑zero, or call a
small helper/assert function that does this) so CI shows which arch check failed
and why.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4438177c-f750-4b47-9859-6acf7fce86ae

📥 Commits

Reviewing files that changed from the base of the PR and between 76f53aa and 4cce891.

📒 Files selected for processing (2)
  • .github/workflows/nightly.yml
  • tests/test_nightly_universal_build.sh
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_nightly_universal_build.sh

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4cce891ae3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

fi
fi
cd ghostty && zig build -Demit-xcframework=true -Demit-macos-app=false -Doptimize=ReleaseFast
cd ghostty && zig build -Demit-xcframework=true -Demit-macos-app=false -Dxcframework-target=universal -Doptimize=ReleaseFast

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Version GhosttyKit release key when changing target

Changing the GhosttyKit build to -Dxcframework-target=universal without changing the artifact identity means xcframework-${sha} can still resolve to an older single-arch release for the same SHA, because the workflow skips rebuilding when that tag already exists and downstream downloads are keyed by SHA. In that case nightly/release universal builds continue consuming a stale non-universal xcframework and can fail when x86_64 slices are required; include the target in the release key (or force a rebuild/migration) so the artifact identity matches its contents.

Useful? React with 👍 / 👎.

Comment thread scripts/setup.sh
(
cd ghostty
zig build -Demit-xcframework=true -Doptimize=ReleaseFast
zig build -Demit-xcframework=true -Dxcframework-target=universal -Doptimize=ReleaseFast

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Invalidate setup cache when switching to universal xcframework

This switches local GhosttyKit builds to universal, but setup.sh cache reuse is still keyed only by GHOSTTY_SHA, so a previously cached/native xcframework for the same SHA will be reused and never rebuilt. Developers who already cached the old artifact can silently keep using a non-universal framework, which breaks or masks universal release behavior locally; include the build target in the cache key/stamp so old native caches are not reused.

Useful? React with 👍 / 👎.

@lawrencecchen
lawrencecchen merged commit 0f29735 into main Mar 8, 2026
18 checks passed
@lawrencecchen
lawrencecchen deleted the task-universal-macos-build branch March 8, 2026 23:02
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
…acos-build

Publish separate universal nightly track

This branch was successfully deployed

1 active deployment
Preview — 4cce891a Deployed Mar 8, 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