From autocraft
Pre-flight check for macOS app UI testing permissions. Sets up a self-signed code signing certificate (via macos-codesign), builds the app, detects required TCC permissions (Screen Recording, Microphone, Accessibility, Automation), guides the user to grant them, and verifies everything works before automated tests run. Use before running journey-builder or any XCUITest suite to prevent permission blockers during AI-driven development.
How this skill is triggered — by the user, by Claude, or both
Slash command
/autocraft:preflight-permissionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Ensure all macOS system permissions are granted before automated UI tests run. This prevents
Ensure all macOS system permissions are granted before automated UI tests run. This prevents XCUITests from hanging on permission dialogs or failing silently when TCC blocks access.
Run this skill once when starting a new project, after cloning, or whenever tests fail
with permission-related errors. It is a prerequisite for journey-builder and journey-loop.
Read project.yml (XcodeGen) or scan *.xcodeproj for:
com.percev.app)PercevUITests)Percev)CODE_SIGN_IDENTITY — if already set to something other than "-", skip certificate creationReport what was found before proceeding.
Use the /macos-codesign skill approach. The certificate name MUST be "{AppName} Dev" (e.g., "Percev Dev").
CERT_NAME="{AppName} Dev"
# Check if it already exists
if security find-identity -v -p codesigning 2>/dev/null | grep -q "$CERT_NAME"; then
echo "Certificate '$CERT_NAME' already exists. Skipping creation."
else
echo "Creating self-signed code signing certificate '$CERT_NAME'..."
echo ">>> You may see a Keychain Access dialog — approve it once. <<<"
cat > /tmp/cert.cfg <<CERT_EOF
[ req ]
distinguished_name = req_dn
[ req_dn ]
CN = $CERT_NAME
[ extensions ]
keyUsage = digitalSignature
extendedKeyUsage = codeSigning
CERT_EOF
openssl req -x509 -newkey rsa:2048 \
-keyout /tmp/dev.key -out /tmp/dev.crt \
-days 3650 -nodes \
-config /tmp/cert.cfg -extensions extensions \
-subj "/CN=$CERT_NAME" 2>/dev/null
security import /tmp/dev.crt -k ~/Library/Keychains/login.keychain-db -T /usr/bin/codesign 2>/dev/null
security import /tmp/dev.key -k ~/Library/Keychains/login.keychain-db -T /usr/bin/codesign 2>/dev/null
security add-trusted-cert -d -r trustRoot -k ~/Library/Keychains/login.keychain-db /tmp/dev.crt 2>/dev/null
rm -f /tmp/cert.cfg /tmp/dev.key /tmp/dev.crt
echo "Certificate '$CERT_NAME' created and trusted."
fi
# Verify it exists
security find-identity -v -p codesigning | grep "$CERT_NAME"
If the certificate was just created or CODE_SIGN_IDENTITY is "-", update the project:
If project.yml exists (XcodeGen):
CODE_SIGN_IDENTITY: "-" to CODE_SIGN_IDENTITY: "{AppName} Dev"xcodegen generate to regenerate the Xcode projectNEVER edit .xcodeproj manually. If no project.yml exists, create one first with xcodegen.
Build the app target to produce a signed binary. Always use -derivedDataPath build so the .app lands in the project root at a predictable path (build/Build/Products/Debug/{AppName}.app). This lets the user easily find and run the app to grant permissions.
xcodebuild build \
-project {Project}.xcodeproj \
-scheme {AppName} \
-destination 'platform=macOS' \
-derivedDataPath build \
-quiet \
2>&1
After a successful build, print the app path so the user knows where it is:
echo "Built app: $(pwd)/build/Build/Products/Debug/{AppName}.app"
If the build fails, diagnose and fix before continuing. Common issues:
security add-trusted-certsecurity unlock-keychain ~/Library/Keychains/login.keychain-dbRead the app's entitlements file and source code to determine which TCC permissions are needed:
| Permission | How to Detect | System Settings Path |
|---|---|---|
| Screen Recording | Entitlement com.apple.security.screen-capture OR uses ScreenCaptureKit/CGWindowList | Privacy & Security > Screen Recording |
| Microphone | Entitlement com.apple.security.device.audio-input OR uses AVCaptureDevice for audio | Privacy & Security > Microphone |
| Accessibility | Uses AXIsProcessTrusted() or Accessibility APIs | Privacy & Security > Accessibility |
| Automation | XCUITest needs Accessibility access to control the app | Privacy & Security > Accessibility |
| Full Disk Access | App reads/writes files outside its container (e.g., ~/AppName/, /tmp/ test fixtures). Without this, macOS shows "would like to access data of other apps" dialog on every launch, blocking unattended UI tests. | Privacy & Security > Full Disk Access |
Also check:
grep -r "SCShareableContent\|SCStreamConfiguration\|CGWindowListCreate" {SourceDir}/ for Screen Recordinggrep -r "AVCaptureDevice\|AVAudioSession\|microphone" {SourceDir}/ for Microphonegrep -r "AXIsProcessTrusted\|AXUIElement" {SourceDir}/ for Accessibility~/AppName/) or /tmp/ directories for test fixtures — if so, Full Disk Access is requiredBuild a checklist of required permissions.
For each required permission, tell the user exactly what to do:
=== PERMISSIONS NEEDED ===
The following permissions must be granted ONCE in System Settings.
After granting, they will persist across rebuilds (thanks to the code signing certificate).
1. [ ] Screen Recording
→ System Settings > Privacy & Security > Screen Recording
→ Add: {AppName} (find it in the app list or use "+")
2. [ ] Microphone
→ System Settings > Privacy & Security > Microphone
→ Toggle ON for {AppName}
3. [ ] Accessibility (for XCUITest automation)
→ System Settings > Privacy & Security > Accessibility
→ Add: Xcode (if not already present)
→ Add: {AppName}
4. [ ] Full Disk Access (prevents "access data of other apps" dialog)
→ System Settings > Privacy & Security > Full Disk Access
→ Add: {AppName}
→ Also add: Xcode.app and/or xcodebuild (if running tests from CLI)
→ Without this, a blocking dialog appears on EVERY app launch during tests
Open System Settings now:
open "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"
open "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"
IMPORTANT: Open each relevant System Settings pane automatically using open commands. Wait for the user to confirm they've granted permissions before proceeding.
Launch the app once so it appears in the TCC permission lists:
# Launch the built app briefly so macOS registers it for TCC permissions
APP_PATH="$(pwd)/build/Build/Products/Debug/{AppName}.app"
echo "Launching $APP_PATH so it appears in System Settings permission lists..."
open "$APP_PATH"
sleep 3
osascript -e 'tell application "{AppName}" to quit'
Tip for the user: You can also run the app manually any time with:
open build/Build/Products/Debug/{AppName}.app
Write a minimal XCUITest that exercises permission-dependent features:
import XCTest
final class PermissionSmokeTests: XCTestCase {
let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
app.launch()
}
override func tearDownWithError() throws {
app.terminate()
}
func testAppLaunchesAndWindowExists() throws {
// Verify the app launches without permission dialogs blocking it
let window = app.windows.firstMatch
XCTAssertTrue(window.waitForExistence(timeout: 10),
"App window should appear — if stuck, check Accessibility permission")
// Take a screenshot to verify no permission dialog is blocking
let screenshot = window.screenshot()
let attachment = XCTAttachment(screenshot: screenshot)
attachment.name = "preflight-001-app-launched"
attachment.lifetime = .keepAlways
add(attachment)
}
func testNoPermissionDialogsOnScreen() throws {
// Check that no system dialog is blocking the app
let window = app.windows.firstMatch
XCTAssertTrue(window.waitForExistence(timeout: 10))
// On macOS, check for alerts/sheets on the app itself (NOT springboard — that's iOS only)
let alert = app.alerts.firstMatch
XCTAssertFalse(alert.waitForExistence(timeout: 3),
"Permission alert detected — grant the permission in System Settings first")
let sheet = app.sheets.firstMatch
XCTAssertFalse(sheet.waitForExistence(timeout: 2),
"Permission sheet detected — grant the permission in System Settings first")
}
}
Place this test in the UI test target if it doesn't already exist. Run it:
xcodebuild test \
-project {Project}.xcodeproj \
-scheme {UITestScheme} \
-destination 'platform=macOS' \
-derivedDataPath build \
-only-testing:{UITestTarget}/PermissionSmokeTests \
-resultBundlePath /tmp/preflight-results.xcresult \
-quiet \
2>&1
Output a clear status report:
=== PREFLIGHT PERMISSIONS REPORT ===
Certificate: ✅ {AppName} Dev (persists across rebuilds)
Build: ✅ Signed with {AppName} Dev
Screen Recording: ✅ Granted (or ❌ NOT granted — tests will hang)
Microphone: ✅ Granted (or ⚠️ Not needed / ❌ NOT granted)
Accessibility: ✅ Granted (or ❌ NOT granted — XCUITest will fail)
Full Disk Access: ✅ Granted (or ❌ NOT granted — "access data" dialog blocks every launch)
Smoke Test: ✅ Passed (or ❌ Failed — see errors above)
Status: READY FOR AUTOMATED TESTING
(or: BLOCKED — fix the items marked ❌ above)
If all checks pass, the project is ready for journey-builder and journey-loop.
project.yml and/or build settings — commit these changes so the team benefitsnpx claudepluginhub sunfmin/autocraftProvides a checklist for code reviews covering functionality, security, performance, maintainability, tests, and quality. Use for pull requests, audits, team standards, and developer training.