diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d5bf5454fa..ca3b072491 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -259,6 +259,111 @@ jobs:
           ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }}
           SIG_PATH: ${{ steps.artifacts.outputs.sig }}
 
+  release-macos-x64:
+    name: Release macOS (Intel)
+    runs-on: macos-latest
+    needs: release
+    timeout-minutes: 60
+    permissions:
+      contents: write
+      id-token: write # required by block/apple-codesign-action for OIDC
+    env:
+      VERSION: ${{ needs.release.outputs.version }}
+      TARGET: x86_64-apple-darwin
+    steps:
+      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
+        with:
+          ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
+          persist-credentials: false
+
+      - uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
+
+      - name: Install desktop dependencies
+        run: just desktop-install-ci
+
+      - name: Add Rust target
+        run: rustup target add "$TARGET"
+
+      - name: Patch version
+        run: |
+          cd desktop && node scripts/set-version-from-tag.mjs "$VERSION"
+          cd src-tauri && cargo update --workspace
+
+      - name: Generate release config
+        run: cd desktop && node scripts/build-release-config.mjs
+        env:
+          SPROUT_UPDATER_PUBLIC_KEY: ${{ secrets.SPROUT_UPDATER_PUBLIC_KEY }}
+          SPROUT_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/sprout-desktop-latest/latest.json
+
+      - name: Build sidecars
+        run: |
+          cargo build --release --target "$TARGET" -p sprout-acp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr -p sprout-cli
+          ./scripts/bundle-sidecars.sh "$TARGET"
+
+      - name: Build unsigned Tauri app
+        run: cd desktop && pnpm tauri build --verbose --no-sign --target "$TARGET" --config src-tauri/tauri.release.conf.json
+        env:
+          SPROUT_UPDATER_PUBLIC_KEY: ${{ secrets.SPROUT_UPDATER_PUBLIC_KEY }}
+          SPROUT_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/sprout-desktop-latest/latest.json
+          TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
+          TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
+          CMAKE_POLICY_VERSION_MINIMUM: "3.5"
+          MACOSX_DEPLOYMENT_TARGET: "10.15"
+          CMAKE_OSX_DEPLOYMENT_TARGET: "10.15"
+
+      - name: Locate unsigned DMG
+        id: unsigned
+        run: |
+          BUNDLE_DIR="desktop/src-tauri/target/${TARGET}/release/bundle"
+          DMG=$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -type f | head -1)
+          if [[ -z "$DMG" ]]; then
+            echo "::error::No DMG found in $BUNDLE_DIR/dmg"
+            exit 1
+          fi
+          echo "dmg=$DMG" >> "$GITHUB_OUTPUT"
+
+      - name: Codesign and Notarize
+        id: codesign
+        uses: block/apple-codesign-action@679535d1ab7c5a7c18e6f9afcba3464512cc3dde # v1.1.0
+        with:
+          osx-codesign-role: ${{ secrets.OSX_CODESIGN_ROLE }}
+          codesign-s3-bucket: ${{ secrets.CODESIGN_S3_BUCKET }}
+          unsigned-artifact-path: ${{ steps.unsigned.outputs.dmg }}
+          entitlements-plist-path: desktop/src-tauri/Entitlements.plist
+          artifact-name: sprout-${{ github.sha }}-${{ github.run_id }}-x64
+
+      - name: Replace DMG and signed .app
+        env:
+          SIGNED_DMG: ${{ steps.codesign.outputs.signed-dmg-path }}
+          SIGNED_APP_ZIP: ${{ steps.codesign.outputs.signed-artifact-path }}
+          UNSIGNED_DMG: ${{ steps.unsigned.outputs.dmg }}
+        run: |
+          set -euo pipefail
+          APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos"
+
+          # Replace the unsigned DMG with the signed/notarized one.
+          cp "$SIGNED_DMG" "$UNSIGNED_DMG"
+
+          # Swap the unsigned .app for the signed .app from the action's zip,
+          # so the verify step below checks the signed bundle (matches arm64).
+          EXTRACT_DIR="${RUNNER_TEMP}/signed-app-extract-x64"
+          rm -rf "$EXTRACT_DIR" && mkdir -p "$EXTRACT_DIR"
+          ditto -x -k "$SIGNED_APP_ZIP" "$EXTRACT_DIR"
+          rm -rf "${APP_DIR}/Sprout.app"
+          cp -R "${EXTRACT_DIR}/Sprout.app" "${APP_DIR}/Sprout.app"
+
+      - name: Verify code signature
+        run: |
+          APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos/Sprout.app"
+          codesign --verify --deep --strict --verbose=2 "$APP_DIR"
+          spctl --assess --type execute --verbose=4 "$APP_DIR"
+
+      - name: Upload Intel DMG to versioned GitHub release
+        run: gh release upload "v${VERSION}" "$DMG_PATH" --clobber
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          DMG_PATH: ${{ steps.unsigned.outputs.dmg }}
+
   release-linux:
     name: Release Linux
     runs-on: ubuntu-latest
diff --git a/RELEASING.md b/RELEASING.md
index 28c56296c4..30a5bccd67 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -88,6 +88,16 @@ Each release produces two GitHub releases:
 
 ---
 
+## Platform Support
+
+The release workflow builds **two separate macOS DMGs** — Apple
+Silicon (`darwin-aarch64`, the `release` job) and Intel
+(`darwin-x86_64`, the `release-macos-x64` job) — plus Linux `.deb` and
+`.AppImage`. Both macOS DMGs are codesigned, notarized, and attached to
+the same `v<version>` release. Intel users download the `_x64.dmg`.
+
+---
+
 ## Prerequisites
 
 - **Write access** to the `block/sprout` GitHub repository
@@ -114,4 +124,8 @@ Commit or stash your changes before running `just release`.
 The version string must be valid semver: `MAJOR.MINOR.PATCH` with an optional pre-release suffix. Do not include a `v` prefix.
 
 ### Auto-updater reports "no update available"
-Verify that the `sprout-desktop-latest` release exists and contains a valid `latest.json`.
+Verify that the `sprout-desktop-latest` release exists and contains a
+valid `latest.json`. The auto-updater manifest currently lists
+`darwin-aarch64` only, so Intel and Linux users do not yet receive
+auto-updates — they download new versions manually from the release
+page. (Adding their entries to `latest.json` is a follow-up.)
diff --git a/scripts/bundle-sidecars.sh b/scripts/bundle-sidecars.sh
index 3d4ad8d760..191aae1151 100755
--- a/scripts/bundle-sidecars.sh
+++ b/scripts/bundle-sidecars.sh
@@ -2,21 +2,30 @@
 set -euo pipefail
 
 SIDECARS=(sprout-acp sprout-agent sprout-dev-mcp git-credential-nostr sprout)
-TARGET=${1:-$(rustc -vV | sed -n 's|host: ||p')}
+HOST=$(rustc -vV | sed -n 's|host: ||p')
+TARGET=${1:-$HOST}
 BINARIES_DIR="desktop/src-tauri/binaries"
 
+# A cross-target build (`cargo build --target <triple>`) emits to
+# target/<triple>/release; a host build emits to target/release.
+if [[ "$TARGET" == "$HOST" ]]; then
+    SRC_DIR="target/release"
+else
+    SRC_DIR="target/${TARGET}/release"
+fi
+
 missing=()
 for bin in "${SIDECARS[@]}"; do
-    [[ -f "target/release/$bin" ]] || missing+=("$bin")
+    [[ -f "$SRC_DIR/$bin" ]] || missing+=("$bin")
 done
 if [[ ${#missing[@]} -gt 0 ]]; then
-    echo "Error: missing release binaries: ${missing[*]}" >&2
+    echo "Error: missing release binaries in $SRC_DIR: ${missing[*]}" >&2
     echo "Run 'cargo build --release -p sprout-acp -p sprout-agent -p sprout-dev-mcp -p git-credential-nostr -p sprout-cli' first." >&2
     exit 1
 fi
 
 mkdir -p "$BINARIES_DIR"
 for bin in "${SIDECARS[@]}"; do
-    cp "target/release/$bin" "$BINARIES_DIR/${bin}-${TARGET}"
+    cp "$SRC_DIR/$bin" "$BINARIES_DIR/${bin}-${TARGET}"
 done
 echo "Sidecars bundled for $TARGET"
