ANTIGRAVITY LABJP
Articles/App Development
App Development/2026-06-25Advanced

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.

xcode-cloudios31cicd8antigravity392app-dev42compute-hourstestflight2

Premium Article

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.

#!/usr/bin/env bash
# ci_usage.sh — fetch recent build runs and aggregate duration per workflow
# Requires: jq, openssl. Pass ASC_KEY_ID / ASC_ISSUER_ID / ASC_P8_PATH as env vars.
 
set -euo pipefail
 
KEY_ID="${ASC_KEY_ID:?}"
ISSUER_ID="${ASC_ISSUER_ID:?}"
P8_PATH="${ASC_P8_PATH:?}"     # e.g. ~/.private_keys/AuthKey_XXXX.p8 (never commit)
 
# --- Generate a short-lived JWT ---
now=$(date +%s); exp=$((now + 600))
header=$(printf '{"alg":"ES256","kid":"%s","typ":"JWT"}' "$KEY_ID" | openssl base64 -A | tr '+/' '-_' | tr -d '=')
payload=$(printf '{"iss":"%s","iat":%d,"exp":%d,"aud":"appstoreconnect-v1"}' "$ISSUER_ID" "$now" "$exp" | openssl base64 -A | tr '+/' '-_' | tr -d '=')
sig=$(printf '%s.%s' "$header" "$payload" | openssl dgst -sha256 -sign "$P8_PATH" | openssl base64 -A | tr '+/' '-_' | tr -d '=')
JWT="${header}.${payload}.${sig}"
 
# --- Pull the last 100 build runs ---
curl -s "https://api.appstoreconnect.apple.com/v1/ciBuildRuns?limit=100&fields[ciBuildRuns]=createdDate,startedDate,finishedDate,executionProgress,completionStatus" \
  -H "Authorization: Bearer ${JWT}" \
| jq -r '
  .data[]
  | select(.attributes.finishedDate != null)
  | { started: .attributes.startedDate, finished: .attributes.finishedDate, status: .attributes.completionStatus }
  | "\(.status)\t\(.started)\t\(.finished)"
'

The startedDatefinishedDate 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.

or
Unlock all articles with Membership →
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.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

App Dev2026-04-04
Automate TestFlight Beta Distribution with Antigravity: A Practical Guide
Automate TestFlight distribution with Antigravity, Fastlane, and GitHub Actions. Covers code signing, CI/CD, and tester management for iOS beta testing.
App Dev2026-06-15
Stop Adding a Ternary Every Time a New iPhone Ships: A Table-Driven Resolution Design
Every time a new resolution arrived—iPhone Air, 17 Pro—I was bolting another screen-size ternary onto a 29-branch pile. Here is how I reshaped that into a table-driven design where adding the next device is a one-line data change, with the actual Swift from my wallpaper apps.
App Dev2026-05-20
Two Weeks of Letting Antigravity Translate Localizable.xcstrings Across 8 Languages
A two-week log of letting Antigravity draft 8-language translations for new keys added to Localizable.xcstrings. What I delegated, what I kept under my own judgment, the unexpected behaviors, and how I reconciled drafts against the existing translation corpus.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →