After remotely opening a large Xcode workspace, there may be no fan to hear, but the CPU graph in the terminal keeps climbing. Code completion remains stuck on “Indexing,” and Jump to Definition returns nothing. The most dangerous response is not waiting—it is immediately deleting the entire development directory. The correct sequence is to determine whether the load comes from compilation or SourceKit, preserve diagnostic evidence, and then rebuild only the affected index.
Distinguish normal rebuilding from a genuine stall
SourceKit rebuilds its index when a project is opened for the first time, after switching Xcode versions, when a dependency lockfile changes, or after moving across many branches. Brief periods of high CPU usage do not necessarily indicate a problem. First stop any active builds, leave the workspace files unchanged, and capture process snapshots two or three times in succession:
date
ps -axo pid,ppid,%cpu,%mem,etime,command | grep -E 'SourceKit|sourcekitd|XCBBuildService|swift-frontend' | grep -v grep
Focus on three things: whether XCBBuildService or swift-frontend is still compiling, whether the SourceKit processes’ elapsed time continues to increase, and whether CPU usage remains concentrated in the same group of processes long after editing has stopped. If build processes are still active, let the build finish first rather than mistaking compilation load for a stalled index.
A single CPU reading is not enough to confirm a stall. Cleanup is warranted only when all four conditions occur together: the file set is stable, no build is running, code completion has made no progress for an extended period, and the logs repeatedly point to the same module.
Preserve comparable diagnostic evidence
Do not restart Xcode first. Create a diagnostic directory containing the process list, logs, and a stack sample so that you can later determine whether the fix was effective.
STAMP="$(date +%Y%m%d-%H%M%S)"
OUT="$HOME/sourcekit-diagnostics/$STAMP"
mkdir -p "$OUT"
ps -axo pid,ppid,%cpu,%mem,etime,command > "$OUT/processes.txt"
log show --last 10m --style compact --predicate 'process CONTAINS[c] "SourceKit" OR process == "Xcode"' > "$OUT/sourcekit.log"
PID="$(pgrep -n -f 'SourceKit|sourcekitd')"
test -n "$PID" && sample "$PID" 10 -file "$OUT/sourcekit.sample.txt"
Do not switch branches or modify files in bulk while the sample is being collected. When reviewing the logs, first look for recurring module names, unreadable paths, plug-in loading failures, and loops of canceled requests. If repeated stack samples show the process parsing the same Swift file or generated file, check whether a script is continuously rewriting that file.
Rule out generated-file loops
A common cause is a code generator that rewrites timestamps every time it runs. Even when the content is unchanged, this forces the indexer to process the file again. The generation script should compare content first and then update the destination with an atomic replacement. Also confirm that the generated directory has not been added to the project both as a folder reference and as a source directory.
Isolate DerivedData for each workspace
Sharing one DerivedData directory allows different branches, Xcode versions, and parallel jobs to contaminate one another. On a cloud Mac, it is more reliable to give each workspace or CI job an explicit path:
ROOT="$PWD"
DERIVED="$ROOT/.derived-data"
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-derivedDataPath "$DERIVED" \
-showBuildSettings > "$ROOT/build-settings.txt"
.derived-data should not be committed to the repository. Parallel jobs should also include a job identifier, such as .derived-data/job-42, to prevent two builds from writing to the module cache at the same time.
| Scenario | DerivedData strategy | Indexing strategy |
|---|---|---|
| Remote interactive development | Fixed, isolated directory for the workspace | Keep enabled |
| One-off validation build | Isolated directory for each job | Enable as needed |
| Build-and-test-only CI | Isolated directory for each job | Index store writes can be disabled |
| Switching Xcode versions | Use a new version-specific directory | Rebuild |
For CI jobs that do not need code completion, add COMPILER_INDEX_STORE_ENABLE=NO to the command line. Do not set it unconditionally in shared project configuration, or developers will lose navigation, diagnostics, and code completion.
Recover in stages according to the affected scope
Before cleanup, close the workspace and verify that no xcodebuild, swift-frontend, or SourceKit process is still using the target directory. First, remove only the index and module cache:
DERIVED="$PWD/.derived-data"
rm -rf "$DERIVED/Index.noindex"
rm -rf "$DERIVED/ModuleCache.noindex"
Reopen the project, keep the branch and dependencies unchanged, and wait for the initial indexing pass to finish. If the problem returns, delete the isolated DerivedData for the current project rather than clearing ~/Library/Developer. If it still recurs, return to the evidence: determine whether only one branch triggers it, whether a generated file keeps changing, or whether a compiler plug-in is incompatible with the current Xcode version.
Do not treat “it worked after restarting the machine” as a diagnosis. A restart only terminates processes; it does not reveal whether the cause was corrupted caches, unstable input files, or a toolchain difference.
Create a reproducible verification checklist
After the fix, repeat validation with the same commit, the same Xcode path, and the same DerivedData strategy. Record the following results:
xcodebuild -versionandxcode-select -ppoint to the expected toolchain.- Indexing completes after the workspace opens, and code completion and Jump to Definition work again.
- SourceKit CPU usage falls after editing and building stop.
- The logs no longer loop on the same file or module.
- The issue does not recur after repeatedly closing and reopening the workspace.
- CI and interactive development use different DerivedData directories.
Remote sessions on NowMini should retain the diagnostic directory together with the build logs, but they must not collect source contents, secrets, or unredacted environment variables. The goal is not to make CPU usage drop to zero immediately. It is to prove that indexing inputs are stable, the processes are making progress, and cleanup affects only the current project.
Frequently asked questions
Does high SourceKit CPU usage always mean indexing is stuck?
No. High usage is expected after opening a large workspace, changing Xcode versions, or updating dependencies. Treat it as a stall only when the same module repeats for an extended period and editor features stop progressing.
Should I delete the entire DerivedData directory?
Usually not. Stop Xcode and active builds, then remove Index.noindex and ModuleCache.noindex only from the workspace-specific DerivedData path. Recreate that project directory only if the issue returns.
Can indexing be disabled for CI-only builds?
Yes. Set COMPILER_INDEX_STORE_ENABLE=NO for noninteractive build or test jobs. Keep indexing enabled for developer sessions that require navigation, diagnostics, and completion.
Run your next task on a dedicated physical node
Use a cloud Mac with M4, 16GB RAM, and a 256GB SSD. Choose a node in Singapore, Tokyo, Seoul, or Hong Kong based on your project schedule.