Engineering Notes

Repair a Corrupted Xcode Build Database on a Cloud Mac

Repair a Corrupted Xcode Build Database on a Cloud Mac

After a remote build node has been running continuously for several days, Xcode may suddenly fail even though the project code has not changed. The log might report that build.db cannot be opened, that the disk image is malformed, or that the database is corrupted, while subsequent attempts continue to stop at the same stage. Deleting all of DerivedData is convenient, but it also removes indexes, module caches, and reusable build artifacts. This slows down later builds and destroys the most valuable evidence from the failure.

On a NUMACS cloud Mac, a safer recovery sequence is to keep the project entry point fixed, preserve the logs, verify that no build process is using the files, check database integrity, and finally rebuild only the target project's XCBuildData.

Confirm That the Failure Comes from the Build Database

Do not diagnose the problem from only the last line shown in Xcode. First, run a command-line build from the same directory with the same scheme and configuration, and save the complete output:

set -o pipefail
mkdir -p "$HOME/build-evidence"

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS Simulator' \
  build 2>&1 | tee "$HOME/build-evidence/xcodebuild.log"

status=${PIPESTATUS[0]}
echo "$status" > "$HOME/build-evidence/exit-code.txt"
exit "$status"

Search specifically for build.db, database, malformed, disk image, and XCBuildData. If the log clearly points to a source compilation error, dependency resolution failure, or insufficient disk space, address that issue first. Not every build failure should be attributed to the database.

Before making repairs, preserve at least the complete log, exit code, and time of the error. Once cleanup is complete, the database's original state usually cannot be reproduced.

Locate the DerivedData Actually Used by the Current Project

A single cloud Mac may contain DerivedData for several projects with the same name. Guessing from directory modification times can easily result in deleting the wrong data. Instead, have xcodebuild return the current build settings, then derive the project root from BUILD_DIR.

BUILD_DIR=$(
  xcodebuild \
    -workspace App.xcworkspace \
    -scheme App \
    -configuration Debug \
    -showBuildSettings |
  awk -F ' = ' '$1 ~ /^[[:space:]]*BUILD_DIR$/ {print $2; exit}'
)

DERIVED_ROOT=$(dirname "$(dirname "$BUILD_DIR")")
XCBD="$DERIVED_ROOT/Build/Intermediates.noindex/XCBuildData"

printf 'DerivedData: %s
XCBuildData: %s
' \
  "$DERIVED_ROOT" "$XCBD"
test -d "$XCBD"

If the pipeline uses -derivedDataPath, reuse that explicit path directly instead of scanning the user directory. This ensures that the script produces the same result in interactive sessions and unattended jobs.

Record the Files to Be Processed

Before cleanup, record the sizes and modification times of the database and related files:

find "$XCBD" -maxdepth 1 -type f \
  -exec stat -f '%Sm %z %N' -t '%Y-%m-%dT%H:%M:%S%z' {} \; \
  > "$HOME/build-evidence/xcbuilddata-files.txt"

This inventory helps determine whether the database was rewritten immediately before the failure. It also makes it easier to establish whether recurring failures affect the same path.

Rule Out Active Processes and Inspect build.db in Read-Only Mode

Do not delete the database while an active build is using it. Check the relevant processes and open file handles first:

pgrep -alf 'Xcode|xcodebuild|XCBBuildService' || true
lsof "$XCBD/build.db" || true

If a build belonging to the current job is still running, stop it through the pipeline's normal cancellation mechanism and wait for its child processes to exit. Do not indiscriminately terminate every matching process on the machine, because valid jobs may still be running in other working directories.

After confirming that the file is no longer in use, inspect the SQLite database in read-only mode:

sqlite3 "file:$XCBD/build.db?mode=ro" \
  'PRAGMA quick_check;'

If the result is ok, no obvious structural corruption was found. Return to the logs and continue investigating permissions, available space, and project configuration. Proceed to rebuilding only if the command returns a nonzero status, cannot read the database, or reports an integrity error.

Check result Next step
lsof shows an active build Stop the job normally and check again
quick_check returns ok Preserve the database and investigate other errors
No active user and the integrity check fails Rebuild the target XCBuildData
The path does not exist Verify the workspace, scheme, and DerivedData arguments

Rebuild Only XCBuildData

Move the damaged directory into the evidence directory instead of permanently deleting it immediately. Perform the move within the same file system to avoid unnecessary copying:

STAMP=$(date '+%Y%m%d-%H%M%S')
QUARANTINE="$HOME/build-evidence/XCBuildData-$STAMP"

mv "$XCBD" "$QUARANTINE"
mkdir -p "$(dirname "$XCBD")"

Then run the build again with the same set of arguments. Xcode will create a new XCBuildData directory and build.db, while preserving the other directories under DerivedData.

set -o pipefail

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS Simulator' \
  build 2>&1 | tee "$HOME/build-evidence/recovery-build.log"

If this minimal rebuild still fails, do not immediately expand the scope of deletion. Compare the old and new logs first to confirm whether the error still points to the database. If the failure has changed to a dependency, permission, or source-code issue, the database problem has been resolved and the new error should be handled on its own terms.

Verify with Two Builds and Prevent Recurrence

A single successful build does not prove that the state is stable. The first build creates the new database; the second incremental build verifies that the database can be read and updated again. Both runs should use the same workspace, scheme, configuration, destination, and DerivedData path, with logs and exit codes saved separately.

Check the following during verification:

  1. A new build.db exists, and a read-only quick_check returns ok.
  2. Both the clean build and the incremental build finish successfully.
  3. The logs no longer contain database format errors or XCBuildData read failures.
  4. The build artifacts come from the expected configuration and target directory.
  5. The original failure evidence remains intact until recovery is confirmed.

Prevention should focus on assigning an explicit DerivedData path to every working directory, not on periodically clearing caches. Job cancellation must also wait for build child processes to exit. Build nodes should continuously monitor available disk space so that an exhausted volume does not leave incomplete files while the database is being written.

If the same path is repeatedly corrupted, record any abnormal termination, disk state, and Xcode version preceding the failure. Do not turn the cleanup command into a standard prerequisite for every build. Resetting the cache every time hides the underlying problem and eliminates the benefits of incremental builds.

The full procedure can ultimately be reduced to four actions: preserve evidence, confirm the path, perform a read-only check, and rebuild only what is necessary. Consider clearing a broader portion of DerivedData only when these steps still cannot restore the build. This approach shortens recovery time while retaining enough information to identify the cause of recurring failures.

Frequently asked questions

Should I delete all DerivedData when build.db fails?

Not as the first response. Stop related builds, preserve the logs, and remove only the affected project's XCBuildData so Xcode can reconstruct the database.

How can I distinguish corruption from an open database file?

Use lsof to find processes holding build.db, then run SQLite quick_check in read-only mode. Treat it as corruption only when no process owns it and validation fails.

How should I verify the repair?

Run one clean build and one incremental build with fixed workspace, scheme, configuration, and DerivedData settings, then retain their exit codes and result bundles.

NUMACS Cloud Mac

Move your builds to a dedicated physical workstation

Both Apple Silicon configurations run on dedicated physical machines, not virtual machines. Rent by the day, week, month, or quarter across Singapore, Tokyo, Seoul, and Hong Kong nodes.

Choose a device and order