When Xcode Cloud's Free 25 Hours Quietly Run Out at Month's End — Field Notes on Measuring and Defending Compute Time
Before Xcode Cloud's free compute allowance drains at month's end and your builds sit queued, measure where it goes. Practical field notes on pulling usage from the App Store Connect API, stopping wasteful builds early, and trimming test runs to keep CI inside budget.
You notice it when a build just won't start near month's end
Around the 25th of one month, I pushed to a release branch and the Xcode Cloud build simply refused to begin. No error. The workflow was still green — it just sat in Queued. The reason was mundane: I had used up that month's free compute allowance of 25 hours.
The awkward part of Xcode Cloud is that crossing the line doesn't announce itself. CI still appears to pass exactly as before; only delivery slips, and at the start of the next month everything springs back to life as if nothing happened. If you have billing enabled, the overage just quietly accrues. For a solo developer, this kind of silent consumption bites harder than a failing test ever does.
This article stays narrow: how to measure where the compute time goes, trim the waste, and keep the allowance intact through the month. It is not about the initial workflow setup — it's about making the cost of a CI pipeline that already runs visible and controllable.
Measure the breakdown first — in numbers, not impressions
Reduction starts with measurement. The Xcode Cloud dashboard shows total usage, but it's hard to trace "which workflow, on which branch, spent how many minutes." This is where pulling ciBuildRuns from the App Store Connect API and aggregating it yourself is faster.
The API uses JWT auth with an API key issued from Users and Access in App Store Connect (Issuer ID, Key ID, and a .p8 private key). Keep the key out of the repository — store it locally or in a CI secret.
The startedDate→finishedDate gap you get here is wall-clock time, not the exact "compute minutes" Apple bills — parallel execution and billing-unit rounding shift it a little. Even so, it's plenty for comparing which workflow occupies the most time. I glance at this output once a week and go after whatever run is conspicuously long, which is usually a UI-test retry or a slow dependency resolve.
To take the aggregation one step further, follow builds from ciBuildRuns, group by workflow name, and sum the minutes per workflow. Rather than building a perfect report up front, it's enough at first to pin down the single workflow eating the most time.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦How to measure which workflow is burning Xcode Cloud compute hours by pulling ciBuildRuns from the App Store Connect API
✦Stopping wasteful builds early with ci_post_clone.sh, plus test-plan splitting and conditional triggers to cut consumption
✦How to decide between defending the free 25-hour tier and switching to paid, using a simple break-even view
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Once you measure, most of the consumption comes from a predictable set of places. In my experience the rough breakdown looks like this.
Source of consumption
Typical share
What helps
UI tests (XCUITest), runs and retries
40–55%
Limit branches, cap parallelism, remove flakiness
Simulator boot and full rebuilds
20–30%
Fix a single destination, stop needless triggers
Swift Package dependency resolution
10–20%
Commit Package.resolved, pin references
UI tests tend to be the biggest culprit because they're heavy to launch and unstable enough to trigger retries. If you have them set to run in full on every commit, compute time melts fast. Unit tests are light enough to run every time, but UI tests call for judgment — run them at integration into develop, or before a release, rather than on every push.
Stop unneeded builds right after clone
The most effective move is to not run at all. Xcode Cloud offers a ci_scripts/ci_post_clone.sh hook that executes right after the repository is fetched. Adding a check there — "if a commit only touched docs, abort the build" — stops wasted runs at the root. Exiting non-zero marks that build as failed, but that is far better than spending the allowance on a change irrelevant to delivery.
#!/usr/bin/env bash# ci_scripts/ci_post_clone.sh — bail out early on commits with no code changesset -euo pipefail# Get the diff against the previous commit (Xcode Cloud may shallow-clone, so deepen)git -C "$CI_PRIMARY_REPOSITORY_PATH" fetch --deepen 1 --quiet || truechanged=$(git -C "$CI_PRIMARY_REPOSITORY_PATH" diff --name-only HEAD~1 HEAD 2>/dev/null || echo "")# If nothing touched source, config, or tests, abortif ! echo "$changed" | grep -Eq '\.(swift|h|m|plist|xcconfig|entitlements|storekit)$|project\.pbxproj|Package\.(swift|resolved)'; then echo "No code changes (changed: ${changed:-none}) — skipping build" exit 1 # non-zero exit stops the buildfiecho "Code changes detected — continuing build"
Commits that only update a README or a screenshot for a blog post are surprisingly common, even in solo development. Simply stopping the full build and UI tests that used to fire on each of those cut my monthly consumption by a noticeable 20–30%. Tune the diff regex to the file types that actually affect a build in your project.
Split the test plans and run a different amount per branch
Rather than packing every test into one .xctestplan, splitting by purpose is easier to operate. You gain the ability to run light unit tests on every commit and heavy UI tests only at integration, decided per workflow.
Concretely, split into UnitTests.xctestplan (fast, all branches) and UITests.xctestplan (slow, only at develop integration and before release). On the Xcode Cloud side, configure unit-only for feature-branch pushes and add UI tests on pull requests into develop. With that, the heavy tests stop firing on the small, frequent pushes you make while building.
Even when you keep UI tests, it matters not to crank -parallel-testing-worker-count carelessly. Raising parallelism shortens wall-clock time, but the extra simulators it boots tend to increase compute minutes instead. If the goal is defending the allowance, "run less often, narrow the scope" beats parallelism.
Have Antigravity read the build log to narrow the cuts
When a long run's cause isn't obvious from the log, pasting the build log into Antigravity and asking it to "list the top three time-consuming stages in this build and suggest cuts" speeds up finding the target. It surfaces the parts humans skim past — a dependency-resolution timeout, simulator boot waits, a retry loop on a particular test.
I also have Antigravity produce the first draft of the workflow design itself, then refine it by hand. Rather than reasoning out every condition from scratch, it's quicker to take a proposed structure and prune it — "this UI test isn't needed on feature branches" — which tends to land on a leaner configuration. The premise is that you don't take the generated proposal at face value; you check it against your measured consumption breakdown before deciding.
Where to draw the line: defend the tier, or go paid
If you've cut everything you can and the allowance still isn't enough, that's also a sign the business has started moving. Letting "staying inside the free tier" become the goal in itself will slow your development cadence. As a rough guide, I frame the decision like this.
Situation
Choice
Overage from over-running UI tests or needless builds
First cut with ci_post_clone and test splitting
Even after cutting, delivery jams at month's end
Switch to paid (weigh it against the cost of delay)
You want one unified CI across platforms
Move Android/Web to a separate CI, keep iOS on Xcode Cloud
Line up the overage charge against the loss of delivery slipping by a few days, and the answer is usually clear. Once your release frequency climbs, letting go of the attachment to the free tier is a healthy call.
Next step: this week, just measure
You don't need to introduce several reduction tactics at once. Run ci_usage.sh once and pin down the single workflow eating the most time. In most cases it's over-running UI tests or full builds on doc updates. Once one cause is visible, the move to make — ci_post_clone.sh or test-plan splitting — settles itself.
Unlike a failure you notice only when something breaks, CI cost piles up quietly. Keeping a single habit of glancing at the consumption breakdown once a month is usually enough to make month-end build jams disappear. Thank you for reading.
Share
Thank You for Reading
Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.