Mobile Development

Your Build Stays Green After 2 December: We Stopped Auditing iOS Dependencies by Whether They Still Install

On 3 December, pod install will still succeed, the lockfile will still resolve, and CI will still be green. What will have quietly gone is the route a fix travels to reach you. We re-ran our iOS dependency audit around that distinction and threw away the check we had been relying on for years.

KAKabir AnandLead Developer
9 min read
Flat vector illustration of a brightly lit library interior with every shelf full, seen through a wall in which the only deposit slot has been covered over with a plain metal plate

From 1 to 7 November the CocoaPods trunk goes read-only for a week as a rehearsal, so that anything automated which still tries to publish fails somewhere visible. On 2 December it goes read-only permanently. Neither date will break your build, and that is the whole problem with the way most of us have been checking iOS dependencies — ourselves included, until we sat down and read what read-only actually means.

A read-only registry is not a dead registry. The Specs repo stays on GitHub, the CDN keeps serving it, and the hundred thousand–plus libraries already published keep resolving exactly as they do today. A clean checkout on a new laptop in March will install. Your lockfile will pin the same versions it pins now. CI will be green. The single thing that stops is publication: after 2 December, nobody can push a new version of a pod to trunk.

Hold those two facts next to each other and the failure mode falls out. The check almost every team runs — does it install, does the build pass — will keep answering yes for a dependency that can no longer receive a security patch. The signal and the risk have come apart, and no part of the toolchain is going to mention it.

A frozen dependency and a healthy one look identical

Install success was always a weak proxy for dependency health. It told you that a version existed, that it resolved against your other constraints, and that it compiled. It never told you whether anyone was still maintaining it, and we all knew that and lived with it, because in practice an abandoned pod and a maintained one were at least on the same road. If a CVE landed, a fix could arrive. You might have to chase it, fork it, or open the pull request yourself, but the route existed.

From December that road is closed for everybody on that channel, maintained or not. A pod whose author is active, responsive and shipping fixes to their repository still cannot put a new version on trunk. The patch will exist in Git and it will not exist where your dependency manager looks for it. This is not a vendor behaving badly — the freeze was announced two years in advance, with the writable registry's own supply-chain exposure given as a reason, and the archive is being kept online rather than deleted. It is a well-signposted change that happens to be invisible to the exact check we were relying on.

The concrete version of this arrived in our inbox this month. Firebase has stopped publishing new Apple SDK versions to CocoaPods: 12.19.0 is the final scheduled release on that channel, and 13.0.0 and later go out through Swift Package Manager and binary distribution only. A project consuming Firebase through CocoaPods right now is not broken. It is finished. It will build happily for months while the SDK underneath it moves on without it, and the first hard signal will be something unrelated and expensive — a new iOS release, an App Store requirement, a push payload that behaves differently than the one you tested.

Editorial photograph of a fully stocked and brightly lit public library reading room at night with every shelf full, and in the foreground a brass wall deposit slot sealed shut by a flat metal plate
Everything already on the shelves stays readable. Nothing new can be put in. Both of those are true at once, and only one of them shows up in CI.

The thing you actually lose is the patch path

What a dependency manager gives you is not code. Code you could vendor into the repository and be done with. What it gives you is a route: a maintained path along which somebody else's fix travels to your build without you having to notice that it was needed. That route is the product. Install success is a side effect of the route having worked recently, which is why it felt like a health check for fifteen years and why it stops being one now.

So we rewrote the audit to ask about the route instead of the result. For every pod that actually ships in a binary we own, one question: if this needs a security fix in February, through which channel does that fix arrive? The script is short enough to be unimpressive, which is roughly the point — nothing here is hard, it just had not occurred to us to ask.

scripts/audit-pod-routes.sh
1#!/usr/bin/env bash
2# One question per pod we ship: if this needs a fix in February,
3# through which channel does that fix arrive? "It installs" is not an answer.
4set -euo pipefail
5 
6pods=$(awk '/^PODS:/,/^DEPENDENCIES:/' Podfile.lock \
7 | grep -oE '^ - [A-Za-z0-9_.+-]+' \
8 | awk '{print $2}' | sort -u)
9 
10for p in $pods; do
11 git=$(pod spec cat "$p" 2>/dev/null | jq -r '.source.git // empty')
12 if [ -z "$git" ]; then echo "UNRESOLVED $p"; continue; fi
13 
14 slug=$(echo "$git" | sed -E 's#https://github.com/(.*)\.git#\1#')
15 if gh api --silent "repos/$slug/contents/Package.swift" 2>/dev/null; then
16 echo "ROUTE-OPEN $p" # a published fix can still reach this build
17 else
18 echo "FROZEN $p $git" # no new version can be published after 2 Dec
19 fi
20done

Two things about the output surprised us. The first was how good the news mostly was: the large, well-known dependencies had shipped a Package.swift long ago, because their maintainers have been reading the same announcements we have. The second was where the frozen ones clustered. They were the small, boring, single-purpose pods — a keychain wrapper, a date formatter, an analytics shim adopted during one project in 2021 — the ones nobody thinks about precisely because they have never caused a problem. A dependency's risk after December has nothing to do with how often it has hurt you and everything to do with whether anyone still publishes it.

“In two years we plan to turn CocoaPods trunk to be read-only. At that point, no new versions or pods will be added to trunk.”

Orta Therox, on the CocoaPods blog, announcing the freeze two years before it happens

We want to be fair to ourselves and to the CocoaPods team about that quote. It is unambiguous, it was published with two years of runway, and we read it at the time. We still did nothing, because every check we had kept saying the dependencies were fine — and they were fine, and they are fine today, and they will be fine on 3 December. A deadline that does not make anything go red does not compete for attention against a deadline that does. That is not a discipline failure, it is a monitoring failure, and the fix is to measure the thing that is actually changing.

Hybrid projects fall back quietly, and that is the design

If you ship Flutter or React Native, this gets one layer worse, because the native dependency manager is behind a wrapper and the wrapper is trying to be helpful. Flutter made Swift Package Manager the default for iOS and macOS as of the 3.44 stable release. You upgrade, you run the app, SwiftPM integration is added for you, and nothing asks you anything. It feels finished.

The sentence that matters is in Flutter's own documentation, and it is not framed as a warning: Flutter falls back to CocoaPods if any of your project's dependencies do not support Swift Package Manager. One plugin without a Package.swift puts the whole project back on the dependency manager you believe you have left. There is no error, no deprecation notice, no line in the build log that a human would stop on. The build succeeds, which is the behaviour you want from a fallback and exactly the behaviour that makes it undetectable.

The ecosystem numbers are genuinely encouraging and they are also the shape of problem that bites. One count published on 11 October had 30 of 34 popular Flutter plugins with native code shipping a Package.swift at their latest release. That is most of the way there, and it means almost nothing for an individual app, because the four that are not ready are not spread evenly across projects — they are concentrated in whichever niche integrations your product happens to need. A ninety per cent ready ecosystem and a fully migrated app are different claims, and only one of them is about you.

Hand-drawn ink sketch of a railway junction where a points lever has switched a train from a newly laid track onto an older weed-grown line running parallel to it
A fallback that works is a fallback you will not notice. The points moved, the train kept going, and the signal stayed green.

So for hybrid projects the audit has an extra step, and it is not a code change. It is reading the generated Xcode project after a build and confirming which dependency manager actually resolved the native layer, then naming the plugins that caused a fallback. We do that on a schedule now rather than once, because the answer changes every time a plugin is upgraded — in both directions. A plugin that gained SwiftPM support can take your project forward without telling you, and the reverse is just as quiet.

The question we ask now, and the one we retired

None of what follows is specific to CocoaPods, which is why we bothered writing it down. Registries go read-only, vendors change distribution channels, and maintainers move platforms. The reusable part is noticing that the question we had been asking was about the artefact when the risk was in the channel.

The four questions that replaced "does it install"

  • Through which channel would a fix for this dependency reach us, and has anything been published on that channel in the last six months? A route with no recent traffic is a route you have not tested.
  • Which of our shipping dependencies cannot receive a new version at all after 2 December, regardless of how active the maintainer is? That list is the actual migration backlog, and it is usually shorter and weirder than the full dependency list.
  • For every hybrid project, which dependency manager resolved the native layer on the last real build — not which one we configured? Check the generated project, not the intent.
  • If we had to take a fix today for a frozen pod, what would we actually do: vendor the source, fork and point at a Git tag, or remove the dependency? Decide per pod now, while nothing is on fire and the maintainer is still responsive.

The order we work in matters more than the speed. Migrating a target to Swift Package Manager is well-documented and mostly mechanical; the part that goes wrong is everything bolted around the old setup. Podfile post-install hooks that rewrite build settings have no direct equivalent and have to be re-expressed deliberately. Custom build phases, resource bundle paths and privacy manifests move. Our rule is that a target is not migrated until a release archive built on a clean runner matches the old one on the things we can diff — entitlements, embedded frameworks, dSYM presence, and startup behaviour on a real device — because a debug build on a developer's machine will hide all four.

  • Do the inventory before the migration— the temptation is to start converting the big obvious dependency first, because it has a migration guide. Run the route audit across every shipping project first instead. It takes an afternoon, it is the only artefact that tells you how much work there is, and it occasionally tells you the answer is almost none.
  • Treat the November rehearsal as free information— if anything in your CI, release tooling or internal pod publishing touches trunk for writes, that week is when it will fail in a context where you are reading logs. A failure you scheduled is worth considerably more than the same failure on a December release day.
  • Decide the exit for each frozen pod while you still have a maintainer — forking a dependency is cheap when the author is around to merge your patch upstream and expensive when the repository has gone quiet. The pods most likely to need this are the small ones you have never had to think about, so this is a deliberate act or it does not happen.

What we are left with is a check that is harder to run and tells the truth. Install success was a cheap, satisfying, continuously verified signal that happened to be measuring the wrong thing, and we kept it long after the announcement that invalidated it, because green is persuasive. The uncomfortable part is that this is not an iOS story. Every ecosystem we work in has a dependency check that confirms an artefact resolved and quietly implies a maintained supply chain behind it, and the two can be separated by a single policy change on somebody else's blog.

Keep Reading

More from the blog

Track Record

The Engineering Partner You Can Build On

Reliable software takes an experienced team that owns delivery end to end. Here’s the track record behind ours.

Book a Free Consultation
01

11+

Years Building Custom Software

02

320+

Projects Delivered Across Web, Mobile & AI

03

85%

Repeat Client Rate

04

12+

Countries Served Worldwide

Trusted by startups and enterprises worldwide

Work With Us

Let’s create
with purpose

Share your goals, timeline, and challenges — we’ll respond with clarity and next steps.

Ambitious ideas deserve thoughtful execution. Start the conversation and let’s define what success looks like.

Team

Acetrum

Est. 2015

4.9/5

Trusted by
top brands

Services interested in:

By submitting, I confirm I’ve read and agree with Privacy and Cookie Policies.

Newsletter

Signals worth
paying attention

No recycled headlines — just the patterns we’re seeing across real client work, distilled into one read a month.

A curated digest of practical thinking and real-world brand perspectives monthly.

No spam. Unsubscribe anytime.