If you run a self-hosted GitHub Actions runner on the same Mac you use every day, do not run the runner under your normal desktop user account.
This is especially important for iOS builds that use Fastlane, match, Xcode signing, or any workflow that calls the macOS security CLI.
A self-hosted runner is not a VM by default. It runs as a normal process on the host, using the home directory, keychain, SSH config, caches, and environment of the user that owns the service.
For iOS builds, Fastlane commonly creates or modifies temporary keychains so it can import certificates and provisioning profiles. If a workflow is cancelled, crashes, or does not restore the keychain state correctly, the desktop user's default keychain or keychain search list can be left pointing at a missing or temporary keychain.
The next time a desktop app tries to store a token or password, macOS may show errors such as:
Keychain Not Found
A keychain cannot be found to store ...
Run the self-hosted runner under a dedicated macOS user, for example:
gha-runner
Keep your daily user account separate from CI.
Recommended properties:
- The runner has its own home directory.
- The runner has its own login keychain.
- The runner is started by a LaunchDaemon or service as the dedicated user.
- CI SSH keys live only under the runner user.
- Fastlane temporary keychains are created under the runner user.
- The workflow restores both
default-keychainandlist-keychainsafter signing work.
For Fastlane-based iOS jobs, add a guard before the build:
set -euo pipefail
LOGIN_KC="$HOME/Library/Keychains/login.keychain-db"
if [ ! -e "$LOGIN_KC" ]; then
echo "::error::login.keychain-db not found: $LOGIN_KC"
exit 1
fi
for kc in ~/Library/Keychains/fastlane-ci-*.keychain-db ~/Library/Keychains/fastlane_tmp_keychain*; do
[ -e "$kc" ] && security delete-keychain "$kc" 2>/dev/null
done
security default-keychain -d user -s "$LOGIN_KC"
security list-keychains -d user -s "$LOGIN_KC"
security default-keychain -d user
security list-keychains -d userAnd in cleanup:
LOGIN_KC="$HOME/Library/Keychains/login.keychain-db"
if [ -e "$LOGIN_KC" ]; then
security default-keychain -d user -s "$LOGIN_KC" 2>/dev/null || true
security list-keychains -d user -s "$LOGIN_KC" 2>/dev/null || true
fi
security delete-keychain "$MATCH_KEYCHAIN_NAME" 2>/dev/null || true
[ -e "$LOGIN_KC" ] && security default-keychain -d user -s "$LOGIN_KC" 2>/dev/null || trueSelf-hosted runners are powerful because they run on your own machine. That also means they can mutate your real machine state.
For macOS signing workflows, treat the runner like infrastructure:
- isolate it with a dedicated user,
- keep signing credentials scoped to that user,
- restore keychain state explicitly,
- and avoid running CI as your daily desktop account.