On Tuesday, 13 October, a little after midnight UTC, every new sign-in to airtaghistory.com started failing.
Apple answered each attempt with
Error -80045: "This action cannot be completed at this time."
Nobody could connect an AirTag, and nobody whose session had expired could reconnect. Users who were already connected kept getting location updates, because background polling never repeats the initial login handshake. Everyone else was completely locked out.
My app made it worse. Deep in the stack, error -80045 was mapped to "bad credentials.".
For most of Tuesday, the site told people their Apple ID password was wrong. It wasn't. Until Tuesday evening they were told it was their fault. This is a post about how it got fixed. Frankly, I could not have fixed it alone. And I don't just mean "it would have taken longer." I mean I wouldn't even have known where to start.
What broke #
When an app signs in to Apple's authentication servers, it has to prove it is running on a legitimate device. It does this by sending a set of device-attestation headers called anisette. They tell Apple's servers "I am this physical device." Apple doesn't document them, and only genuine Apple software can generate them.
The open-source community solved this years ago with an ingenious hack: extract Apple's proprietary libraries from Apple Music for Android and run them on Linux servers inside Docker containers. Public anisette servers do exactly that. SideStore, AltStore, FindMy.py, my own exporter: every tool in the ecosystem quietly relied on this emulated Android anisette.
On Tuesday, Apple closed that door. They started rejecting emulated Android anisette whenever the client claimed to be an Apple device. And because Find My only works on Apple hardware, claiming to be an Android phone isn't an option. The downstream endpoints reject you.
By Tuesday evening, the AltStore tracking issue for -80045 was filling up. The maintainer called it Apple-side. Developers traded workarounds that worked for one account, once, without 2FA. My users all have 2FA.
Two agents, one problem #
That evening, I did something I hadn't tried before: I put two different coding agents on the same crisis.
Claude Code (Opus 5.5 xhigh) started with research. I had it spawn several subagents: one to crawl GitHub issues and forums for anyone who had found a crack, and others to trace exactly where in our auth pipeline the request died. They converged on a consistent story: Apple had added an attestation check targeting the very first step of the SRP handshake.
I didn't let it stop there. When the summary read as settled, I told it: don't save this as "confirmed root cause." It was a strong hypothesis, not a proof. Hardening a guess into project memory would mislead every future session.
Codex (GPT-6.1 Sol) got the findings doc and one instruction: prepare a fix.
To understand why this is a nightmare, you have to realize that signing into iCloud to extract AirTag keys isn't a single API call. It's a seven-stage gauntlet:
- Fetch anisette headers
- Sign in to the Apple ID (the SRP handshake, plus 2FA)
- Fetch the MobileMe delegate
- Set up CloudKit and the keychain
- Join the iCloud Keychain trust circle (escrow)
- Fetch the Find My accessories from CloudKit
- Assemble them into usable AirTag key files
Codex spent the night trying to salvage Android anisette. I ran each build locally with my own Apple ID and pasted the logs back. Every run crawled forward and then died at stage 3: Apple returned a partial token with no CloudKit access and no keychain. Exactly the dead end the rest of the community was hitting.
Wednesday morning, Codex changed tack. It wrote a small probe that queried macOS itself for anisette through a private framework shipped on every Mac. Then it built a test binary to sign in with those native headers.
Stage 3 passed. CloudKit token, keychain config, everything.
Then stage 5 failed: error -3001, Authentication failed from Apple's escrow service. Codex traced the request and caught a bizarre mismatch: our Rust auth library had signed in as a Mac, but when building the escrow join request, it quietly swapped the client identity to an iPhone. Apple noticed the switch and refused.
Codex patched the identity, wrote a regression test, and rebuilt. I ran it at 09:29 my time:
Done! Exported 10 accessory plist file(s) to ./keys
All seven stages green. My AirTags, my luggage tag, my keys, my MacBooks. As far as I could tell, it was the first complete sign-in anyone in our corner of the ecosystem had achieved since Tuesday.
The part the agents couldn't decide #
A working binary on my development laptop is not a product. airtaghistory.com runs on Railway, in Linux containers. And the only proven fix required genuine macOS hardware.
Both agents spent hours trying to architect around this. Codex stood up a Linux container running Wine, loaded Apple's Windows iCloud libraries, and got as far as establishing a remote desktop on Railway. Then it asked me to sign in to my personal iCloud account inside that VNC window.
I stopped. Why on earth would I enter my personal Apple ID and password into a cloud VNC session?
My personal account had no business initializing a server that handles other people's data. Customers' accounts certainly didn't belong there either. The agent was doing what models do: optimizing aggressively to make the immediate next step pass. The product's security and trust model was mine to protect.
The other route, reverse-engineering how Windows provisions its local identity blob, was stopped by a safety classifier halfway through Claude's response. In hindsight, fair enough.
So I went to the drawer.
I had an old 2017 15-inch MacBook Pro sitting idle on macOS Ventura. My first question to Claude was: "Should I sign in to my Apple ID on that Mac?"
Claude's answer was no. The Mac's machine ID becomes the keychain peer ID for every sign-in it serves, so the Mac must not sit in anyone's iCloud Keychain circle, mine included. It had to stay account-less.
From there, Claude Code built the production system in a single session:
- mac-anisette : A minimal standard-library HTTP service running on that Mac that returns its seven native anisette headers. It lives behind a long secret path, because whoever holds the URL can sign in as that Mac. Every other path returns a 404.
- A Cloudflare Tunnel so Railway can reach it securely, paired with a launch agent that keeps the laptop awake and serving with the lid closed.
- An outside-in synthetic check in Grafana Cloud that pings the endpoint and emails me immediately if the old laptop stops responding.
- A new exporter flag , --anisette-headers-url, treating the Mac as an isolated, typed seam. Leave it unset and nothing changes.
- Secret redaction across the entire pipeline, because the upstream libraries love logging request URLs.
It shipped as three pull requests on Wednesday afternoon, all merged by Wednesday night.
About 45 hours after Apple flipped the switch, people could connect again.
Faster than the community #
I want to be very clear: the open-source developers behind FindMy.py, rustpush, and the anisette servers are the heroes of this story. They did years of deep reverse engineering that I could never have done myself, and my product exists because of their work.
Yet as I write this, the main AltStore tracking issue is still open. Another tool's thread was closed without a fix. Nobody has posted a working server-side login. A solo developer with zero background in Apple's auth stack had a fix live in production in under two days.
That wasn't because I'm smarter than those maintainers. It came down to two things:
- Two tireless agents: I had frontier models working in parallel around the clock. They could read rustpush internals, write Swift against undocumented macOS frameworks, patch Rust crates, and configure Cloudflare and Railway, at 3 a.m.
- Asymmetric constraints: General-purpose open-source tools have to work for tens of thousands of users on arbitrary hardware. They can't demand that everyone own a spare physical Mac. But I don't run a general framework; I run a single product with a narrow goal. I could accept a trade-off an open-source project never could
What I'd tell you #
1. Use two agents, not one. Opus did the architectural research and the disciplined production build. Codex tackled the low-level native headers and caught the escrow bug overnight. Neither would have reached the finish line alone in that timeframe. They also audited each other: I had Claude review Codex's session transcript and code before anything was merged.
2. The agents write the code. You hold the perimeter. Every decision that mattered was a judgment call no model made for me: refusing to turn a hypothesis into fact, refusing to type my credentials into a cloud VNC, keeping the Mac out of any iCloud account, and choosing an old laptop over a clever Wine hack. That's the same lesson as my last post, at higher stakes.
3. Fix the message before the bug. The worst part of this outage wasn't the downtime. It was telling people their password was wrong when Apple was refusing everyone. The first pull request that landed wasn't the fix. It was the one that stopped blaming the user.