Engineering note

Diagnose Cloud Mac Build and Signing Failures with macOS Unified Logging

DevOps & CI/CD ·~5 min read

Diagnose Cloud Mac Build and Signing Failures with macOS Unified Logging

An overnight automated build suddenly fails, leaving only Command PhaseScriptExecution failed in the terminal, yet succeeds when rerun. At that point, staring at the final line will not help. On a cloud Mac, build tools, signing services, network processes, and system permission components all write to the macOS unified log. Placing those events on a single timeline can usually reveal whether the failure originated in a project script, a system service, or a dropped remote connection.

Establish the Incident Window and Reproduction Boundaries

First, record the node time, command start time, failure time, and exit code. The local time shown by a remote client may differ from the node’s time zone, so use the time reported by the node consistently throughout the investigation.

date -u '+%Y-%m-%dT%H:%M:%SZ'
set -o pipefail
xcodebuild \
  -workspace Demo.xcworkspace \
  -scheme Demo \
  -configuration Release \
  build 2>&1 | tee "$HOME/build-output.log"
printf 'exit=%s
' "${PIPESTATUS[0]}"

Do not immediately clear DerivedData, restart processes, or overwrite the original logs. Copy the build output first, then determine whether the failure can be reproduced consistently. For intermittent issues, capture at least two runs and note whether both used the same commit, Xcode path, shell environment, and working directory.

Unified logging does not replace terminal output. The unified log explains what happened at the system and service level, while terminal output shows how far the build command progressed. Both records must cover the same time window.

Narrow the Unified Log with Predicates

Use log show to inspect events that have already occurred. Do not begin by exporting several hours of unfiltered logs. That creates substantial noise and increases the effort required to sanitize the data. Start with a ten-minute window on either side of the failure and filter by process name:

log show \
  --last 20m \
  --style compact \
  --info \
  --debug \
  --predicate 'process == "xcodebuild" OR process == "codesign"'

If the terminal reports only a signing failure, search for rejection messages returned by security services. If a remote session may have affected the build, inspect sshd and the process that actually ran the script. Prefer predicate fields such as process, subsystem, category, and eventMessage instead of relying only on broad full-text matching.

log show --last 15m --style compact \
  --predicate '(process == "sshd") OR (eventMessage CONTAINS[c] "denied")'

The following table provides a starting point for interpreting common signals, but any conclusion must still be validated against the exit code and reproduction steps.

Log signal Check first Conclusion not to jump to
permission denied File permissions, Keychain access, execution account Node hardware failure
Process terminated Memory pressure, parent process, script timeout rules Xcode itself is corrupted
SSH session disconnected Client network, keepalive settings, task supervision method The build necessarily failed
No matching item from the signing service Keychain, certificate name, provisioning profile mapping Reinstall the entire toolchain

Monitor Key Events During Reproduction

When the issue can be reproduced reliably, open a second SSH session and run log stream. Use the live stream only for observation, and keep the filter narrow enough that continuous output does not bury the relevant events.

log stream \
  --style compact \
  --level info \
  --predicate 'process == "xcodebuild" OR process == "codesign" OR process == "securityd"'

Then start the build in the original session and record the time between the first anomalous log entry and the build failure. If a signing error appears first and the build tool later summarizes it as a generic failure, investigate the signing path first. If SSH disconnects while the build process continues running, determine whether the task is tied to an interactive terminal rather than misclassifying the incident as a compilation error.

Add Correlation Markers to Scripts

For scripts you control, write markers to the unified log at the beginning and end of each run. Use a fixed subsystem and a unique job identifier for every execution. This avoids relying on imprecise filename searches.

job_id="$(date -u '+%Y%m%dT%H%M%SZ')"
logger -p user.notice -t minid-build "job=$job_id phase=start"
./scripts/build.sh
status=$?
logger -p user.notice -t minid-build "job=$job_id phase=end status=$status"
exit "$status"

The tag written by logger can be queried through process or the message contents. Use the job identifier only to correlate a single run; it must not contain tokens, repository credentials, or customer data.

Export Evidence That Is Reviewable and Safe to Share

Plain-text command output works well for quick analysis, while more complex incidents should be preserved as a .logarchive. Confirm the time range again before collecting the archive to avoid including unrelated historical records.

mkdir -p "$HOME/diagnostics"
sudo log collect \
  --last 15m \
  --output "$HOME/diagnostics/build-incident.logarchive"

cp "$HOME/build-output.log" "$HOME/diagnostics/"

Do not send the entire directory without reviewing it first. Inspect the build log for expanded environment variables, absolute project paths, node addresses, repository URLs, certificate names, and script arguments. When replacing sensitive values, preserve a consistent mapping—for example, always replace the same username with <USER>. Otherwise, later analysis cannot determine whether multiple events belong to the same account.

Also prepare a plain-text incident note containing the UTC time, command executed, exit code, expected result, actual result, reproducibility status, and the commit used by the last successful run before the failure. Logs establish what happened; the incident note defines the scope of the investigation.

Choose the Next Action Based on Evidence

After collecting the evidence, classify the issue as project-related, environment-related, connection-related, or resource-related. For project issues, test the same commit in a clean working directory. For environment issues, verify the selected Xcode installation, shell initialization, and Keychain visibility. For connection issues, distinguish a disconnected session from a terminated task. For resource issues, inspect available disk space, memory pressure, and concurrent processes.

Before closing the investigation, perform a minimal acceptance check: build the same commit twice in succession and confirm that both runs exit with code zero; validate signed artifacts with codesign --verify --deep --strict; confirm that background tasks still finish as expected after SSH disconnects; and verify that the diagnostics directory contains no plaintext tokens. The incident is fully resolved only when all of these conditions are satisfied.

Frequently asked questions

Why is xcodebuild output sometimes insufficient?

It primarily records build steps. Permission denials, signing service faults, network disconnects, and system process errors may appear only in macOS Unified Logging.

What should be removed before sharing logs?

Mask usernames, absolute project paths, node addresses, repository URLs, tokens, certificate names, and any identifiable workload data before sending the archive.

MiniD Cloud Mac

Rent a dedicated physical Mac mini by the day, week, or month

Every plan runs on a dedicated physical Mac mini with remote desktop and SSH access; current models, regions, and terms are shown on the order page.

View ordering options