From 639e7ff99749a5236bd6749e1573c7707970c327 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 26 May 2026 22:01:21 +0200 Subject: [PATCH] fix(mac): harden restart and dSYM packaging --- CHANGELOG.md | 2 ++ scripts/package-mac-dist.sh | 50 +++++++++++++++++++++------ scripts/restart-mac.sh | 17 +++++---- test/scripts/package-mac-dist.test.ts | 6 ++++ test/scripts/restart-mac.test.ts | 15 ++++++++ 5 files changed, 73 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e210162f016..02e28b7e6058 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ Docs: https://docs.openclaw.ai ### Fixes +- Mac: prefer the freshly packaged app in restart flows and collect dSYMs only for requested build architectures so stale installed apps and unrequested arch symbol directories cannot break validation. + ## 2026.5.26 ### Changes diff --git a/scripts/package-mac-dist.sh b/scripts/package-mac-dist.sh index d167fb9bd48b..8d669735ad6e 100755 --- a/scripts/package-mac-dist.sh +++ b/scripts/package-mac-dist.sh @@ -19,6 +19,11 @@ APP_VERSION_INPUT="${APP_VERSION:-$(cd "$ROOT_DIR" && node -p "require('./packag # Default to universal binary for distribution builds (supports both Apple Silicon and Intel Macs) export BUILD_ARCHS="${BUILD_ARCHS:-all}" export BUILD_CONFIG +DSYM_ARCHS_VALUE="$BUILD_ARCHS" +if [[ "$DSYM_ARCHS_VALUE" == "all" ]]; then + DSYM_ARCHS_VALUE="arm64 x86_64" +fi +IFS=' ' read -r -a DSYM_ARCHS <<< "$DSYM_ARCHS_VALUE" # Use release bundle ID (not .debug) so Sparkle auto-update works. # The .debug suffix in package-mac-app.sh blanks SUFeedURL intentionally for dev builds. @@ -136,24 +141,49 @@ else fi if [[ "$SKIP_DSYM" != "1" ]]; then - DSYM_ARM64="$(find "$BUILD_ROOT/arm64" -type d -path "*/$BUILD_CONFIG/$PRODUCT.dSYM" -print -quit)" - DSYM_X86="$(find "$BUILD_ROOT/x86_64" -type d -path "*/$BUILD_CONFIG/$PRODUCT.dSYM" -print -quit)" - if [[ -n "$DSYM_ARM64" || -n "$DSYM_X86" ]]; then + DSYM_PATHS=() + MISSING_DSYM_ARCHS=() + for arch in "${DSYM_ARCHS[@]}"; do + if [[ ! -d "$BUILD_ROOT/$arch" ]]; then + MISSING_DSYM_ARCHS+=("$arch") + continue + fi + DSYM_FOR_ARCH="$(find "$BUILD_ROOT/$arch" -type d -path "*/$BUILD_CONFIG/$PRODUCT.dSYM" -print -quit)" + if [[ -n "$DSYM_FOR_ARCH" ]]; then + DSYM_PATHS+=("$DSYM_FOR_ARCH") + else + MISSING_DSYM_ARCHS+=("$arch") + fi + done + + if [[ "${#MISSING_DSYM_ARCHS[@]}" -gt 0 ]]; then + echo "Error: dSYM not found for architecture(s): ${MISSING_DSYM_ARCHS[*]} (set SKIP_DSYM=1 to skip symbols)" >&2 + exit 1 + fi + + if [[ "${#DSYM_PATHS[@]}" -gt 0 ]]; then TMP_DSYM="$ROOT_DIR/dist/$PRODUCT.dSYM" rm -rf "$TMP_DSYM" - if [[ -n "$DSYM_ARM64" && -n "$DSYM_X86" ]]; then - cp -R "$DSYM_ARM64" "$TMP_DSYM" + if [[ "${#DSYM_PATHS[@]}" -gt 1 ]]; then + cp -R "${DSYM_PATHS[0]}" "$TMP_DSYM" DWARF_OUT="$TMP_DSYM/Contents/Resources/DWARF/$PRODUCT" - DWARF_ARM="$DSYM_ARM64/Contents/Resources/DWARF/$PRODUCT" - DWARF_X86="$DSYM_X86/Contents/Resources/DWARF/$PRODUCT" - if [[ -f "$DWARF_ARM" && -f "$DWARF_X86" ]]; then - /usr/bin/lipo -create "$DWARF_ARM" "$DWARF_X86" -output "$DWARF_OUT" + DWARF_INPUTS=() + for dsym in "${DSYM_PATHS[@]}"; do + DWARF_INPUT="$dsym/Contents/Resources/DWARF/$PRODUCT" + if [[ ! -f "$DWARF_INPUT" ]]; then + echo "Error: missing DWARF binaries for dSYM merge (set SKIP_DSYM=1 to skip symbols)" >&2 + exit 1 + fi + DWARF_INPUTS+=("$DWARF_INPUT") + done + if [[ "${#DWARF_INPUTS[@]}" -gt 1 ]]; then + /usr/bin/lipo -create "${DWARF_INPUTS[@]}" -output "$DWARF_OUT" else echo "Error: missing DWARF binaries for dSYM merge (set SKIP_DSYM=1 to skip symbols)" >&2 exit 1 fi else - cp -R "${DSYM_ARM64:-$DSYM_X86}" "$TMP_DSYM" + cp -R "${DSYM_PATHS[0]}" "$TMP_DSYM" fi echo "🧩 dSYM: $DSYM_ZIP" rm -f "$DSYM_ZIP" diff --git a/scripts/restart-mac.sh b/scripts/restart-mac.sh index e7de40daf3ff..e9cf0cca92a6 100755 --- a/scripts/restart-mac.sh +++ b/scripts/restart-mac.sh @@ -188,13 +188,11 @@ fi run_step "package app" bash -lc "cd '${ROOT_DIR}' && SKIP_TSC=${SKIP_TSC:-1} '${ROOT_DIR}/scripts/package-mac-app.sh'" choose_app_bundle() { - if [[ -n "${APP_BUNDLE}" && -d "${APP_BUNDLE}" ]]; then - return 0 - fi - - if [[ -d "/Applications/OpenClaw.app" ]]; then - APP_BUNDLE="/Applications/OpenClaw.app" - return 0 + if [[ -n "${APP_BUNDLE}" ]]; then + if [[ -d "${APP_BUNDLE}" ]]; then + return 0 + fi + fail "OPENCLAW_APP_BUNDLE does not exist: ${APP_BUNDLE}" fi if [[ -d "${ROOT_DIR}/dist/OpenClaw.app" ]]; then @@ -205,6 +203,11 @@ choose_app_bundle() { return 0 fi + if [[ -d "/Applications/OpenClaw.app" ]]; then + APP_BUNDLE="/Applications/OpenClaw.app" + return 0 + fi + fail "App bundle not found. Set OPENCLAW_APP_BUNDLE to your installed OpenClaw.app" } diff --git a/test/scripts/package-mac-dist.test.ts b/test/scripts/package-mac-dist.test.ts index c07052c53c7e..a150bbab4e33 100644 --- a/test/scripts/package-mac-dist.test.ts +++ b/test/scripts/package-mac-dist.test.ts @@ -83,6 +83,12 @@ describe("package-mac-dist plist validation", () => { const script = readFileSync(scriptPath, "utf8"); const dsymBlock = script.slice(script.indexOf('if [[ "$SKIP_DSYM" != "1" ]]')); + expect(dsymBlock).toContain('for arch in "${DSYM_ARCHS[@]}"'); + expect(dsymBlock).toContain('if [[ ! -d "$BUILD_ROOT/$arch" ]]; then'); + expect(dsymBlock).toContain('MISSING_DSYM_ARCHS+=("$arch")'); + expect(dsymBlock).toContain("Error: dSYM not found for architecture(s):"); + expect(dsymBlock).not.toContain('find "$BUILD_ROOT/arm64"'); + expect(dsymBlock).not.toContain('find "$BUILD_ROOT/x86_64"'); expect(dsymBlock).toContain("Error: missing DWARF binaries for dSYM merge"); expect(dsymBlock).toContain("Error: dSYM not found"); expect(dsymBlock).toContain("exit 1"); diff --git a/test/scripts/restart-mac.test.ts b/test/scripts/restart-mac.test.ts index 451fccb8e0a0..45e421fe6c2e 100644 --- a/test/scripts/restart-mac.test.ts +++ b/test/scripts/restart-mac.test.ts @@ -73,4 +73,19 @@ describe("scripts/restart-mac.sh", () => { ); expect(script).not.toContain("lsof -iTCP:${GATEWAY_PORT} -sTCP:LISTEN | head -n 5 || true"); }); + + it("prefers the freshly packaged app unless an explicit app bundle is set", () => { + const script = readFileSync(restartScriptPath, "utf8"); + const chooseBlock = script.slice( + script.indexOf("choose_app_bundle()"), + script.indexOf("choose_app_bundle", script.indexOf("choose_app_bundle()") + 1), + ); + + expect(chooseBlock).toContain('fail "OPENCLAW_APP_BUNDLE does not exist: ${APP_BUNDLE}"'); + expect(chooseBlock.indexOf('${ROOT_DIR}/dist/OpenClaw.app')).toBeGreaterThan(-1); + expect(chooseBlock.indexOf('/Applications/OpenClaw.app')).toBeGreaterThan(-1); + expect(chooseBlock.indexOf('${ROOT_DIR}/dist/OpenClaw.app')).toBeLessThan( + chooseBlock.indexOf('/Applications/OpenClaw.app'), + ); + }); });