I'm trying to set up a CI/CD pipeline for a flutter desktop app on Github Actions. While the build for windows works fine, the macos job keeps on hanging (I set a timeout at 30 mins). By setting verbose on flutter build and modifying the autogenerated scripts to dump logs in a file I was able to determine the step it's stuck on is the codesign (I manage certificates with fastlane match), but I can't find any errors in the log, and running the same command locally works fine. The job is defined like this:
build_macos:
if: ${{ github.actor != 'github-actions' }}
needs: mac_check
runs-on: macos-latest
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_KEY_P8: ${{ secrets.ASC_KEY_P8 }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
MATCH_STORAGE_MODE: "google_cloud"
GCLOUD_KEY_JSON: ${{ secrets.GCLOUD_KEY_JSON }}
APP_IDENTIFIER: "<APP_IDENTIFIER>"
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
steps:
- name: Skip if disabled
if: needs.mac_check.outputs.macos_build != 'true'
run: echo "Skipping MacOS Build"
- uses: actions/checkout@v3
if: needs.mac_check.outputs.macos_build == 'true'
- name: Setup Flutter
if: needs.mac_check.outputs.macos_build == 'true'
uses: subosito/flutter-action@v2
with:
flutter-version-file: pubspec.yaml
- name: Enable macOS Desktop
if: needs.mac_check.outputs.macos_build == 'true'
run: flutter config --enable-macos-desktop
- name: Check Flutter installation and path
run: |
which flutter
flutter doctor -v
- name: Print macos_assemble.sh content
run: |
cat /Users/runner/hostedtoolcache/flutter/stable-3.32.5-arm64/packages/flutter_tools/bin/macos_assemble.sh
- name: Clean ephemeral and cache
if: needs.mac_check.outputs.macos_build == 'true'
run: |
flutter clean
rm -rf macos/Flutter/ephemeral
- name: Install dependencies
if: needs.mac_check.outputs.macos_build == 'true'
run: flutter pub get
- name: Install fastlane
if: needs.mac_check.outputs.macos_build == 'true'
run: |
gem install fastlane
which fastlane
- name: Save GCS credentials
if: needs.mac_check.outputs.macos_build == 'true'
run: |
mkdir -p .credentials
echo "${GCLOUD_KEY_JSON}" | base64 --decode > .credentials/credentials.json
echo "GOOGLE_APPLICATION_CREDENTIALS=$PWD/.credentials/credentials.json" >> $GITHUB_ENV
- name: Run Fastlane macos_signing lane
if: needs.mac_check.outputs.macos_build == 'true'
working-directory: macos
run: fastlane macos_signing --verbose
- name: List code signing identities
run: |
security find-identity -v -p codesigning
- name: Build Flutter macOS release
if: needs.mac_check.outputs.macos_build == 'true'
timeout-minutes: 30
continue-on-error: true
run: |
flutter build macos --release --build-number=${{ github.run_number }} -v --split-debug-info=build/debug-info
- name: Show assemble_debug.log
if: always()
run: |
LOG_FILE="${{ github.workspace }}/macos/Flutter/ephemeral/assemble_debug.log"
if [ -f "$LOG_FILE" ]; then
echo "Contents of assemble_debug.log:"
cat "$LOG_FILE"
else
echo "No assemble_debug.log file found."
fi
- name: Run Fastlane release lane
if: needs.mac_check.outputs.macos_build == 'true'
working-directory: macos
run: fastlane upload_macos_build --verbose
- name: Upload macOS installer for gh release
if: github.ref == 'refs/heads/main' && needs.mac_check.outputs.macos_build == 'true'
uses: actions/upload-artifact@v4
with:
name: macos-installer
path: build/macos/Build/Products/Release/*.app
and this is the content of the fastfile (initially I was calling flutter build from there, I put it in a separate step of the build just to see which step was stuck):
default_platform(:macos)
platform :macos do
before_all do
load_asc_api_token
end
desc "Load the App Store Connect API token"
lane :load_asc_api_token do
app_store_connect_api_key(
key_id: ENV["ASC_KEY_ID"],
issuer_id: ENV["ASC_ISSUER_ID"],
key_content: ENV["ASC_KEY_P8"],
is_key_content_base64: true,
in_house: false
)
end
desc "Release a new macOS build to TestFlight"
lane :release_beta do
commit = last_git_commit
puts "*** Starting macOS release for commit(#{commit[:abbreviated_commit_hash]}) ***"
api_key = lane_context[SharedValues::APP_STORE_CONNECT_API_KEY]
sync_code_signing(
api_key: api_key,
app_identifier: "<APP_IDENTIFIER>",
type: "appstore",
readonly: true
)
build_number = ENV["GITHUB_RUN_NUMBER"] || Time.now.strftime("%Y%m%d%H%M")
puts "*** Build Flutter macOS app for build number #{build_number} ***"
Dir.chdir("../..") do
sh("flutter", "build", "macos", "--release", "--build-number=#{build_number}")
end
puts "*** Create .pkg for notarization ***"
app_path = "../../build/macos/Build/Products/Release/ivaservizi.app"
pkg_path = "ivaservizi.pkg"
sh("productbuild", "--component", app_path, "/Applications", pkg_path)
puts "*** Upload to TestFlight (App Store Connect) ***"
upload_to_testflight(
api_key: api_key,
skip_waiting_for_build_processing: true,
pkg: pkg_path
)
end
lane :macos_signing do
api_key = lane_context[SharedValues::APP_STORE_CONNECT_API_KEY]
sync_code_signing(
api_key: api_key,
app_identifier: "<APP_IDENTIFIER>",
type: "appstore",
readonly: true
)
end
desc "Upload a prebuilt macOS app to TestFlight"
lane :upload_macos_build do
app_path = "../../build/macos/Build/Products/Release/ivaservizi.app"
pkg_path = "ivaservizi.pkg"
unless File.exist?(app_path)
UI.user_error!("Missing built app at #{app_path}. Run `flutter build macos` first.")
end
load_asc_api_token
api_key = lane_context[SharedValues::APP_STORE_CONNECT_API_KEY]
puts "*** Create .pkg for notarization ***"
sh("productbuild", "--component", app_path, "/Applications", pkg_path)
puts "*** Upload to TestFlight (App Store Connect) ***"
upload_to_testflight(
api_key: api_key,
skip_waiting_for_build_processing: true,
pkg: pkg_path
)
end
end
I checked the certificates used by the workflow with security find-identity -v -p codesigning and they seem correct. I'm honestly stumped, I can't see any issues from the workflow log that could at least point me in the right direction.
-
1Usually this type of hang is caused by some UI showing on the runner that in invisible in logs. For example some Keychain access confirmations or iCloud account login confirmation.Grzegorz Krukowski– Grzegorz Krukowski2025年07月04日 08:13:36 +00:00Commented Jul 4, 2025 at 8:13
1 Answer 1
The issue was with keychain access. I was able to solve it by creating a temporary one and unlocking it. I also had some trouble getting code signing and notarization to work, so here's the full job definition and fastfile for reference:
build_macos:
if: ${{ github.actor != 'github-actions' }}
needs: mac_check
runs-on: macos-latest
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_KEY_P8: ${{ secrets.ASC_KEY_P8 }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
MATCH_STORAGE_MODE: "google_cloud"
GCLOUD_KEY_JSON: ${{ secrets.GCLOUD_KEY_JSON }}
APP_IDENTIFIER: "<APP IDENTIFIER>"
TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
steps:
- name: Skip if disabled
if: needs.mac_check.outputs.macos_build != 'true'
run: echo "Skipping MacOS Build"
- uses: actions/checkout@v3
if: needs.mac_check.outputs.macos_build == 'true'
- name: Setup Flutter
if: needs.mac_check.outputs.macos_build == 'true'
uses: subosito/flutter-action@v2
with:
flutter-version-file: pubspec.yaml
- name: Enable macOS Desktop
if: needs.mac_check.outputs.macos_build == 'true'
run: flutter config --enable-macos-desktop
- name: Check Flutter installation and path
run: |
which flutter
flutter doctor -v
- name: Create temporary keychain
id: create_keychain
if: needs.mac_check.outputs.macos_build == 'true'
run: |
KEYCHAIN_NAME=temp_build.keychain-db
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
echo "Creating temporary keychain: $KEYCHAIN_NAME"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
security set-keychain-settings -lut 21600 "$KEYCHAIN_NAME"
security default-keychain -s "$KEYCHAIN_NAME"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_NAME"
security list-keychains -d user -s "$KEYCHAIN_NAME"
echo "password=$KEYCHAIN_PASSWORD" >> $GITHUB_OUTPUT
echo "FASTLANE_KEYCHAIN_PATH=$KEYCHAIN_NAME" >> $GITHUB_ENV
echo "FASTLANE_KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> $GITHUB_ENV
- name: Print macos_assemble.sh content
run: |
cat /Users/runner/hostedtoolcache/flutter/stable-3.32.5-arm64/packages/flutter_tools/bin/macos_assemble.sh
- name: Clean ephemeral and cache
if: needs.mac_check.outputs.macos_build == 'true'
run: |
flutter clean
rm -rf macos/Flutter/ephemeral
- name: Install dependencies
if: needs.mac_check.outputs.macos_build == 'true'
run: flutter pub get
- name: Install fastlane
if: needs.mac_check.outputs.macos_build == 'true'
run: |
gem install fastlane
which fastlane
- name: Save GCS credentials
if: needs.mac_check.outputs.macos_build == 'true'
run: |
mkdir -p .credentials
echo "${GCLOUD_KEY_JSON}" | base64 --decode > .credentials/credentials.json
echo "GOOGLE_APPLICATION_CREDENTIALS=$PWD/.credentials/credentials.json" >> $GITHUB_ENV
- name: Run Fastlane macos_signing lane
if: needs.mac_check.outputs.macos_build == 'true'
working-directory: macos
env:
FASTLANE_KEYCHAIN_PASSWORD: ${{ needs.build_macos.outputs.keychain_password }}
run: fastlane macos_signing --verbose
- name: Setting up keychain for codesign and productbuild
if: needs.mac_check.outputs.macos_build == 'true'
run: |
security set-key-partition-list -S apple-tool:,apple: -s -k "$FASTLANE_KEYCHAIN_PASSWORD" "$FASTLANE_KEYCHAIN_PATH"
- name: Run Fastlane release lane
if: needs.mac_check.outputs.macos_build == 'true'
working-directory: macos
env:
FASTLANE_KEYCHAIN_PASSWORD: ${{ needs.build_macos.outputs.keychain_password }}
run: fastlane release_beta --verbose
- name: Upload macOS installer for gh release
if: github.ref == 'refs/heads/main' && needs.mac_check.outputs.macos_build == 'true'
uses: actions/upload-artifact@v4
with:
name: macos-installer
path: build/macos/Build/Products/Release/*.app
- name: Delete temporary keychain
if: always() && needs.mac_check.outputs.macos_build == 'true'
run: |
KEYCHAIN_NAME=temp_build.keychain-db
echo "Deleting keychain $KEYCHAIN_NAME"
security delete-keychain "$KEYCHAIN_NAME" || echo "Keychain not found or already deleted"
default_platform(:mac)
platform :mac do
before_all do
load_asc_api_token
end
desc "Load the App Store Connect API token"
lane :load_asc_api_token do
app_store_connect_api_key(
key_id: ENV["ASC_KEY_ID"],
issuer_id: ENV["ASC_ISSUER_ID"],
key_content: ENV["ASC_KEY_P8"],
is_key_content_base64: true,
in_house: false
)
end
desc "Release a new macOS build to TestFlight"
lane :release_beta do
commit = last_git_commit
puts "*** Starting macOS release for commit(#{commit[:abbreviated_commit_hash]}) ***"
api_key = lane_context[SharedValues::APP_STORE_CONNECT_API_KEY]
macos_signing
build_number = ENV["GITHUB_RUN_NUMBER"] || Time.now.strftime("%Y%m%d%H%M")
puts "*** Build Flutter macOS app for build number #{build_number} ***"
Dir.chdir("../..") do
sh("flutter", "build", "macos", "--release", "--build-number=#{build_number}")
end
increment_build_number({build_number:build_number})
build_mac_app(
configuration: "Release",
skip_package_pkg: false,
output_directory: ".build",
# xcodebuild_formatter: "xcbeautify",
silent: true,
export_method: "app-store",
export_options: {
provisioningProfiles: {
"<APP IDENTIFIER>" => "match AppStore <APP IDENTIFIER> macos"
}
}
)
puts "*** Upload to TestFlight (App Store Connect) ***"
upload_to_testflight(
api_key: api_key,
skip_waiting_for_build_processing: true
)
end
lane :macos_signing do
api_key = lane_context[SharedValues::APP_STORE_CONNECT_API_KEY]
keychain_name = ENV["FASTLANE_KEYCHAIN_PATH"] || "login.keychain-db"
keychain_password = ENV["FASTLANE_KEYCHAIN_PASSWORD"] || ""
sync_code_signing(
api_key: api_key,
app_identifier: "<APP IDENTIFIER>",
type: "appstore",
readonly: true,
platform: "macos",
keychain_name: keychain_name,
keychain_password: keychain_password,
additional_cert_types: ["mac_installer_distribution", "developer_id_installer"]
)
end
end
Comments
Explore related questions
See similar questions with these tags.