Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 2 additions & 19 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ concurrency:
permissions:
contents: write

env:
CREATE_DMG_VERSION: 8.0.0

jobs:
decide:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -126,7 +123,7 @@ jobs:

- name: Install build deps
run: |
npm install --global "create-dmg@${CREATE_DMG_VERSION}"
brew list create-dmg >/dev/null 2>&1 || brew install create-dmg

- name: Download pre-built GhosttyKit.xcframework
run: |
Expand Down Expand Up @@ -312,8 +309,6 @@ jobs:
local dmg_release="$2"
local dmg_immutable="$3"
local zip_submit="${dmg_release%.dmg}-notary.zip"
local dmg_tmp_dir
local created_dmg

ditto -c -k --sequesterRsrc --keepParent "$app_path" "$zip_submit"
APP_SUBMIT_JSON="$(xcrun notarytool submit "$zip_submit" --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --wait --output-format json)"
Expand All @@ -328,19 +323,7 @@ jobs:
xcrun stapler validate "$app_path"
spctl -a -vv --type execute "$app_path"
rm -f "$zip_submit"

dmg_tmp_dir="$(mktemp -d)"
create-dmg \
--identity="$APPLE_SIGNING_IDENTITY" \
"$app_path" \
"$dmg_tmp_dir"
created_dmg="$(find "$dmg_tmp_dir" -maxdepth 1 -name '*.dmg' | head -n 1)"
if [ -z "$created_dmg" ]; then
echo "Failed to locate created DMG for $app_path" >&2
exit 1
fi
mv "$created_dmg" "$dmg_release"
rm -rf "$dmg_tmp_dir"
CMUX_CREATE_DMG_REQUIRE_STYLED=1 ./scripts/create_release_dmg.sh "$app_path" "$dmg_release" "$APPLE_SIGNING_IDENTITY"

DMG_SUBMIT_JSON="$(xcrun notarytool submit "$dmg_release" --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --wait --output-format json)"
DMG_SUBMIT_ID="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"$DMG_SUBMIT_JSON")"
Expand Down
81 changes: 69 additions & 12 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ on:
permissions:
contents: write

env:
CREATE_DMG_VERSION: 8.0.0

jobs:
build-sign-notarize:
runs-on: depot-macos-latest
Expand All @@ -21,7 +18,24 @@ jobs:
with:
submodules: recursive

- name: Determine release mode
id: release_mode
run: |
set -euo pipefail
if [[ "${GITHUB_EVENT_NAME}" == "push" && "${GITHUB_REF:-}" == refs/tags/* ]]; then
echo "publish_release=true" >> "$GITHUB_OUTPUT"
echo "release_tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
echo "artifact_name=release-${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
else
REF_SLUG="$(printf '%s' "${GITHUB_REF_NAME}" | tr '/[:space:]' '-' | tr -cd '[:alnum:]-_.')"
SHORT_SHA="${GITHUB_SHA::7}"
echo "publish_release=false" >> "$GITHUB_OUTPUT"
echo "release_tag=verify-${SHORT_SHA}" >> "$GITHUB_OUTPUT"
echo "artifact_name=release-verification-${REF_SLUG}-${SHORT_SHA}" >> "$GITHUB_OUTPUT"
fi

- name: Guard immutable release assets
if: steps.release_mode.outputs.publish_release == 'true'
id: guard_release_assets
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
Expand Down Expand Up @@ -99,13 +113,23 @@ jobs:
- name: Install build deps
if: steps.guard_release_assets.outputs.skip_all != 'true'
run: |
npm install --global "create-dmg@${CREATE_DMG_VERSION}"
brew list create-dmg >/dev/null 2>&1 || brew install create-dmg

- name: Download pre-built GhosttyKit.xcframework
if: steps.guard_release_assets.outputs.skip_all != 'true'
run: |
./scripts/download-prebuilt-ghosttykit.sh

- name: Verify GhosttyKit architectures
if: steps.guard_release_assets.outputs.skip_all != 'true'
run: |
set -euo pipefail
GHOSTTYKIT_BINARY="GhosttyKit.xcframework/macos-arm64_x86_64/libghostty.a"
test -f "$GHOSTTYKIT_BINARY"
GHOSTTYKIT_ARCHS="$(lipo -archs "$GHOSTTYKIT_BINARY")"
echo "GhosttyKit architectures: $GHOSTTYKIT_ARCHS"
[[ "$GHOSTTYKIT_ARCHS" == *arm64* && "$GHOSTTYKIT_ARCHS" == *x86_64* ]]

- name: Cache Swift packages
if: steps.guard_release_assets.outputs.skip_all != 'true'
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
Expand All @@ -131,9 +155,37 @@ jobs:
if: steps.guard_release_assets.outputs.skip_all != 'true'
run: |
xcodebuild -scheme cmux -configuration Release -derivedDataPath build \
-destination 'generic/platform=macOS' \
-clonedSourcePackagesDirPath .spm-cache \
ARCHS="arm64 x86_64" \
ONLY_ACTIVE_ARCH=NO \
CODE_SIGNING_ALLOWED=NO build

- name: Verify release binary architectures
if: steps.guard_release_assets.outputs.skip_all != 'true'
run: |
set -euo pipefail
APP_BINARY="build/Build/Products/Release/cmux.app/Contents/MacOS/cmux"
CLI_BINARY="build/Build/Products/Release/cmux.app/Contents/Resources/bin/cmux"
APP_ARCHS="$(lipo -archs "$APP_BINARY")"
CLI_ARCHS="$(lipo -archs "$CLI_BINARY")"
if [[ "${{ steps.release_mode.outputs.publish_release }}" == "true" ]]; then
RELEASE_MODE="publish"
else
RELEASE_MODE="verify"
fi
echo "App binary architectures: $APP_ARCHS"
echo "CLI binary architectures: $CLI_ARCHS"
[[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]]
[[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]]
{
echo "Release mode: $RELEASE_MODE"
echo "Ref: ${GITHUB_REF_NAME}"
echo "Commit: ${GITHUB_SHA}"
echo "App binary architectures: $APP_ARCHS"
echo "CLI binary architectures: $CLI_ARCHS"
} > release-verification.txt

- name: Inject Sparkle keys into Info.plist
if: steps.guard_release_assets.outputs.skip_all != 'true'
run: |
Expand Down Expand Up @@ -218,12 +270,7 @@ jobs:
xcrun stapler validate "$APP_PATH"
spctl -a -vv --type execute "$APP_PATH"
rm -f "$ZIP_SUBMIT"
# create-dmg generates a styled drag-to-install DMG
create-dmg \
--identity="$APPLE_SIGNING_IDENTITY" \
"$APP_PATH" \
./
mv ./cmux*.dmg "$DMG_RELEASE"
CMUX_CREATE_DMG_REQUIRE_STYLED=1 ./scripts/create_release_dmg.sh "$APP_PATH" "$DMG_RELEASE" "$APPLE_SIGNING_IDENTITY"
DMG_SUBMIT_JSON="$(xcrun notarytool submit "$DMG_RELEASE" --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --wait --output-format json)"
DMG_SUBMIT_ID="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["id"])' <<<"$DMG_SUBMIT_JSON")"
DMG_STATUS="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["status"])' <<<"$DMG_SUBMIT_JSON")"
Expand Down Expand Up @@ -258,10 +305,10 @@ jobs:
echo "Missing SPARKLE_PRIVATE_KEY secret" >&2
exit 1
fi
./scripts/sparkle_generate_appcast.sh cmux-macos.dmg "$GITHUB_REF_NAME" appcast.xml
./scripts/sparkle_generate_appcast.sh cmux-macos.dmg "${{ steps.release_mode.outputs.release_tag }}" appcast.xml

- name: Upload release asset
if: steps.guard_release_assets.outputs.skip_upload != 'true'
if: steps.release_mode.outputs.publish_release == 'true' && steps.guard_release_assets.outputs.skip_upload != 'true'
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
with:
files: |
Expand All @@ -270,6 +317,16 @@ jobs:
generate_release_notes: true
overwrite_files: false

- name: Upload verification artifacts
if: steps.release_mode.outputs.publish_release != 'true' && steps.guard_release_assets.outputs.skip_all != 'true'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: ${{ steps.release_mode.outputs.artifact_name }}
path: |-
cmux-macos.dmg
appcast.xml
release-verification.txt

- name: Cleanup keychain
if: always()
run: |
Expand Down
25 changes: 22 additions & 3 deletions scripts/build-sign-upload.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ APP_PATH="build/Build/Products/Release/cmux.app"
# --- Pre-flight ---
source ~/.secrets/cmuxterm.env
export SPARKLE_PRIVATE_KEY
for tool in zig xcodebuild create-dmg xcrun codesign ditto gh; do
for tool in zig xcodebuild xcrun codesign ditto gh; do
command -v "$tool" >/dev/null || { echo "MISSING: $tool" >&2; exit 1; }
done
echo "Pre-flight checks passed"
Expand All @@ -68,12 +68,31 @@ else
echo "GhosttyKit.xcframework exists, skipping build"
fi

GHOSTTYKIT_BINARY="GhosttyKit.xcframework/macos-arm64_x86_64/libghostty.a"
test -f "$GHOSTTYKIT_BINARY"

@cubic-dev-ai cubic-dev-ai Bot Mar 10, 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: test -f will silently abort (via set -e) with no error message if the binary is missing. Add a diagnostic so the operator knows why the build failed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/build-sign-upload.sh, line 72:

<comment>`test -f` will silently abort (via `set -e`) with no error message if the binary is missing. Add a diagnostic so the operator knows *why* the build failed.</comment>

<file context>
@@ -68,6 +68,12 @@ else
 fi
 
+GHOSTTYKIT_BINARY="GhosttyKit.xcframework/macos-arm64_x86_64/libghostty.a"
+test -f "$GHOSTTYKIT_BINARY"
+GHOSTTYKIT_ARCHS="$(lipo -archs "$GHOSTTYKIT_BINARY")"
+echo "GhosttyKit architectures: $GHOSTTYKIT_ARCHS"
</file context>
Suggested change
test -f "$GHOSTTYKIT_BINARY"
test -f "$GHOSTTYKIT_BINARY" || { echo "ERROR: universal GhosttyKit binary not found at $GHOSTTYKIT_BINARY" >&2; exit 1; }
Fix with Cubic

GHOSTTYKIT_ARCHS="$(lipo -archs "$GHOSTTYKIT_BINARY")"
echo "GhosttyKit architectures: $GHOSTTYKIT_ARCHS"
[[ "$GHOSTTYKIT_ARCHS" == *arm64* && "$GHOSTTYKIT_ARCHS" == *x86_64* ]]

@cubic-dev-ai cubic-dev-ai Bot Mar 10, 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 architecture assertion silently exits if GhosttyKit isn't universal. Add an error message so the failure is self-explanatory.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/build-sign-upload.sh, line 75:

<comment>The architecture assertion silently exits if GhosttyKit isn't universal. Add an error message so the failure is self-explanatory.</comment>

<file context>
@@ -68,6 +68,12 @@ else
+test -f "$GHOSTTYKIT_BINARY"
+GHOSTTYKIT_ARCHS="$(lipo -archs "$GHOSTTYKIT_BINARY")"
+echo "GhosttyKit architectures: $GHOSTTYKIT_ARCHS"
+[[ "$GHOSTTYKIT_ARCHS" == *arm64* && "$GHOSTTYKIT_ARCHS" == *x86_64* ]]
+
 # --- Build app (Release, unsigned) ---
</file context>
Suggested change
[[ "$GHOSTTYKIT_ARCHS" == *arm64* && "$GHOSTTYKIT_ARCHS" == *x86_64* ]]
[[ "$GHOSTTYKIT_ARCHS" == *arm64* && "$GHOSTTYKIT_ARCHS" == *x86_64* ]] || { echo "ERROR: GhosttyKit is not universal (got: $GHOSTTYKIT_ARCHS)" >&2; exit 1; }
Fix with Cubic


# --- Build app (Release, unsigned) ---
echo "Building app..."
rm -rf build/
xcodebuild -scheme cmux -configuration Release -derivedDataPath build CODE_SIGNING_ALLOWED=NO build 2>&1 | tail -5
xcodebuild -scheme cmux -configuration Release -derivedDataPath build \
-destination 'generic/platform=macOS' \
ARCHS="arm64 x86_64" \
ONLY_ACTIVE_ARCH=NO \
CODE_SIGNING_ALLOWED=NO build 2>&1 | tail -5
echo "Build succeeded"

APP_BINARY="$APP_PATH/Contents/MacOS/cmux"
CLI_BINARY="$APP_PATH/Contents/Resources/bin/cmux"

@cubic-dev-ai cubic-dev-ai Bot Mar 10, 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 CLI binary path is used unconditionally here, but the codesign step later guards with if [ -f "$CLI_PATH" ], implying it may be absent. Either guard this block similarly, or remove the guard from the codesign step if the CLI is now always expected in release builds.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/build-sign-upload.sh, line 82:

<comment>The CLI binary path is used unconditionally here, but the codesign step later guards with `if [ -f "$CLI_PATH" ]`, implying it may be absent. Either guard this block similarly, or remove the guard from the codesign step if the CLI is now always expected in release builds.</comment>

<file context>
@@ -71,9 +71,22 @@ fi
 echo "Build succeeded"
 
+APP_BINARY="$APP_PATH/Contents/MacOS/cmux"
+CLI_BINARY="$APP_PATH/Contents/Resources/bin/cmux"
+APP_ARCHS="$(lipo -archs "$APP_BINARY")"
+CLI_ARCHS="$(lipo -archs "$CLI_BINARY")"
</file context>
Fix with Cubic

APP_ARCHS="$(lipo -archs "$APP_BINARY")"
CLI_ARCHS="$(lipo -archs "$CLI_BINARY")"
Comment on lines +89 to +90

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the relevant sections of the build script
cat -n scripts/build-sign-upload.sh | head -100

Repository: manaflow-ai/cmux

Length of output: 3844


🏁 Script executed:

# Also check if lipo is mentioned elsewhere in the file
rg -n "lipo" scripts/build-sign-upload.sh

Repository: manaflow-ai/cmux

Length of output: 147


🏁 Script executed:

# Find the tool gate check section
rg -n "for tool in" scripts/build-sign-upload.sh -A 5

Repository: manaflow-ai/cmux

Length of output: 294


Add lipo to the pre-flight dependency check.

Lines 83-84 use lipo -archs as a hard requirement, but the tool gate check (line 56) doesn't include it. This means a missing lipo will only be caught after the build finishes, not at preflight.

🔧 Suggested fix
-for tool in zig xcodebuild create-dmg xcrun codesign ditto gh; do
+for tool in zig xcodebuild create-dmg xcrun codesign ditto gh lipo; do
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/build-sign-upload.sh` around lines 83 - 84, The script later calls
lipo to compute APP_ARCHS and CLI_ARCHS but the preflight tool check does not
include lipo, so add lipo to the dependency/tools list used by the preflight
check (the same place where other required tools are validated—e.g., the tools
array or check_tools/check_dependencies function) so the script verifies lipo is
present before the build proceeds; this ensures the lipo binary is validated
alongside the other tools and will fail fast if missing.

echo "App binary architectures: $APP_ARCHS"
echo "CLI binary architectures: $CLI_ARCHS"
[[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]]
[[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]]
Comment on lines +93 to +94

@cubic-dev-ai cubic-dev-ai Bot Mar 10, 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: These bare [[ ]] assertions exit silently under set -e with no error message. Add an || { echo ...; exit 1; } clause so operators can immediately see which binary is missing an architecture.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/build-sign-upload.sh, line 87:

<comment>These bare `[[ ]]` assertions exit silently under `set -e` with no error message. Add an `|| { echo ...; exit 1; }` clause so operators can immediately see which binary is missing an architecture.</comment>

<file context>
@@ -71,9 +71,22 @@ fi
+CLI_ARCHS="$(lipo -archs "$CLI_BINARY")"
+echo "App binary architectures: $APP_ARCHS"
+echo "CLI binary architectures: $CLI_ARCHS"
+[[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]]
+[[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]]
+
</file context>
Suggested change
[[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]]
[[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]]
[[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]] || { echo "ERROR: App binary is not universal (got: $APP_ARCHS)" >&2; exit 1; }
[[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]] || { echo "ERROR: CLI binary is not universal (got: $CLI_ARCHS)" >&2; exit 1; }
Fix with Cubic

Comment on lines +93 to +94

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.

Silent arch assertion failures lack diagnostic output

When either binary is missing an architecture, these [[ ... ]] assertions fail with exit code 1 (due to set -euo pipefail on line 2), but produce no error message. Developers only see a generic "exited with code 1", making triage difficult. This same issue exists in .github/workflows/release.yml lines 172–173.

Adding explicit error messages makes failures immediately diagnosable:

Suggested change
[[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]]
[[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]]
if ! [[ "$APP_ARCHS" == *arm64* && "$APP_ARCHS" == *x86_64* ]]; then
echo "ERROR: app binary is not universal (got: $APP_ARCHS)" >&2; exit 1
fi
if ! [[ "$CLI_ARCHS" == *arm64* && "$CLI_ARCHS" == *x86_64* ]]; then
echo "ERROR: CLI binary is not universal (got: $CLI_ARCHS)" >&2; exit 1
fi


# --- Inject Sparkle keys ---
echo "Injecting Sparkle keys..."
SPARKLE_PUBLIC_KEY_DERIVED=$(swift scripts/derive_sparkle_public_key.swift "$SPARKLE_PRIVATE_KEY")
Expand Down Expand Up @@ -107,7 +126,7 @@ echo "App notarized"
# --- Create and notarize DMG ---
echo "Creating DMG..."
rm -f cmux-macos.dmg
create-dmg --codesign "$SIGN_HASH" cmux-macos.dmg "$APP_PATH"
CMUX_CREATE_DMG_REQUIRE_STYLED=1 ./scripts/create_release_dmg.sh "$APP_PATH" "cmux-macos.dmg" "$SIGN_HASH"
echo "Notarizing DMG..."
xcrun notarytool submit cmux-macos.dmg \
--apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PASSWORD" --wait
Expand Down
Loading
Loading