The code has not changed, yet the second Xcode build on a cloud Mac still compiles a large number of targets. The same commit behaves normally again when built in a fresh workspace. Problems like this do not always originate in DerivedData. Source files, generated files, or cached artifacts may carry incorrect modification times. Clearing the cache may temporarily remove the symptoms, but timestamp drift will return to the workspace if the synchronization and restoration process remains unchanged.
Confirm That Timestamp Drift Is the Cause
First, fix the commit, Scheme, Xcode path, and build parameters, then run the same build twice in succession. Allow the first build to generate all intermediate artifacts, and observe which tasks still run during the second build. If the same Swift compilation, resource processing, or script phases run repeatedly, compare the results from a clean workspace with those from a reused workspace.
Do not focus only on total build time during diagnosis. Record the input paths, script outputs, and generated directories involved in repeated tasks, and check whether any tool rewrites files before the build starts. Common indicators include:
- File modification times later than the current system time;
- Outputs that become newer or older than the source files after a cache is extracted;
- Generation scripts that overwrite files with identical content on every run;
- Synchronization tools that preserve incorrect timestamps from another machine;
- Correct timestamps on the workspace volume but inconsistent metadata inside archives.
Incremental builds depend on more than file contents. Once the chronological relationship between inputs and outputs becomes unreliable, the build system may repeatedly decide that completed tasks need to run again.
Scan for Future-Dated Files and Suspicious Directories
Check the system time and time zone first, then scan for files dated more than five minutes into the future. A five-minute tolerance avoids very small collection errors while remaining sufficient to identify clear timestamp drift.
#!/bin/zsh
set -euo pipefail
root="${1:-$PWD}"
limit=$(( $(date +%s) + 300 ))
find "$root" -type f -print0 |
while IFS= read -r -d '' file; do
modified=$(stat -f '%m' "$file")
if (( modified > limit )); then
printf '%s\t%s\n' \
"$(date -r "$modified" '+%Y-%m-%d %H:%M:%S %z')" \
"$file"
fi
done
At minimum, scan the source code, project files, scripts, resources, code-generation directories, and restored caches. Do not begin by scanning the entire user directory, because package manager caches, logs, and system files will create substantial noise. For each matching file, also run stat -x 文件路径 to verify its modification time, change time, and volume.
If the anomalies are concentrated in one directory, you can usually narrow the cause down to a specific download, extraction, synchronization, or code-generation step. If they are spread throughout the repository, inspect how the workspace is copied and review any initialization scripts that run before the job begins.
Choose the Repair Based on the Source
Different causes require different remedies. Do not conceal them all with touch.
| Symptom | Common source | Recommended action |
|---|---|---|
| A small number of source files are dated in the future | Faulty archive or synchronization source | Retrieve the files again and verify the source machine's clock |
| Generated files receive a new timestamp on every run | Generator overwrites files unconditionally | Replace files atomically only after their contents change |
| Timestamp relationships within a cache directory are inconsistent | Incorrect metadata was preserved during restoration | Discard that cache set and rebuild the cache key |
| The entire workspace is dated near the copy time | Copy options altered the metadata | Standardize the checkout method and avoid mixing synchronization strategies |
| Outputs remain older than inputs | Timestamps inside the cache archive are incorrect | Regenerate the cache and validate its manifest |
For source code managed by Git, the safest repair is usually to confirm that there are no uncommitted changes and then check out the affected directory again, rather than rewriting timestamps across the entire repository. For generated files, have the generator write to a temporary file first, compare the contents, and replace the original only when necessary:
generate_config > Config.generated.swift.tmp
if ! cmp -s Config.generated.swift.tmp Config.generated.swift; then
mv Config.generated.swift.tmp Config.generated.swift
else
rm Config.generated.swift.tmp
fi
When the contents have not changed, this approach preserves the original file's modification time and prevents dependent compilation tasks from being triggered unnecessarily.
Inspect Cache and Synchronization Boundaries
Before restoring a cache, define which directories may be reused across jobs. Build artifacts, module caches, and package manager caches have different lifecycles and should not be bundled into one large, untraceable archive. At minimum, the cache manifest should record the cache key, Xcode version, architecture, generation command, and file count. After restoration, run a timestamp preflight check before allowing the main build phase to begin.
The synchronization tool's handling of modification times must also be explicit. Preserving timestamps helps incremental build decisions, but it also propagates an incorrect clock from the source machine unchanged. Not preserving them can make every file appear newly modified. The key is not to mandate one particular option, but to have the team use a single validated strategy and encode it in the job scripts.
If code is transferred as an archive, extract it into an isolated directory first. Scan for future-dated files, verify the file count, and check the Git status before atomically switching it into the active workspace. Do not directly overwrite a directory that still contains old intermediate artifacts.
Add a Time Baseline to the Job Entry Point
Ultimately, the check should run before dependency resolution and the Xcode build. Fail immediately when future-dated files are found and print only a limited number of paths. Do not automatically adjust the timestamps and continue building, because doing so erases evidence of the failure.
Retain the following records for every job:
- Output from
dateandsystemsetup -gettimezone; - Current commit, Xcode path, and SDK information;
- Number of future-dated files and the first several matching paths;
- Cache key and restoration result;
- Differences between tasks run during the first and second builds.
After applying the fix, run the same commit three times in succession. Use the first run to establish the cache, the second to verify incremental build behavior, and the third to confirm that the result was not accidental. If the second and third runs still repeat the same task, continue tracing that task's input files instead of performing another full cleanup. The goal of timestamp management is not to give every file the same time, but to maintain an explainable and verifiable chronological relationship among source files, generated artifacts, and caches.
Frequently asked questions
Should I run touch across the whole repository after finding a future-dated file?
No. That changes every modification time and usually triggers a broad rebuild. Identify the source, then repair only the affected files or check out the affected directory again.
Why is a clean build stable while incremental builds keep slowing down?
Incremental builds compare existing outputs and dependency state. A future-dated input, clock rollback, or cache restored with inconsistent metadata can invalidate that state on every run.
Configure a Cloud Mac for Your Next Development Task
Choose Oak M4, Oak M4 Plus, or Oak M4 Pro, then configure a rental plan across five physical nodes based on your team's location.