Apple Approved My App. One of Its Login Buttons Was Still Broken.
Tendera's iOS app went live on July 12.
It had passed its builds. It had gone through TestFlight. Apple had reviewed it over four rounds. The final version was approved and available in the App Store.
The Google login button had never successfully logged anyone in.
Not intermittently. Not under a rare account condition. The entire path was broken, and had been broken when the app shipped.
I found out the way no release process should rely on: I tapped the button myself after launch.
What the User Saw
The user tapped Continue with Google, completed what looked like the beginning of a normal sign-in, and arrived back at the login screen.
No error. No explanation. No next step.
From the outside, several different outcomes looked identical:
The interface treated the last outcome as harmless and hid its message. Unfortunately, the browser flow could also surface a failure as a cancellation result. The code swallowed it exactly as designed.
The user did not see a broken integration. They saw a button that returned them to where they started.
It Was Two Bugs, Not One
The first bug was somewhere in a browser-based OAuth chain with too many boundaries:
Any failure in that chain could collapse into the same quiet result. I investigated several plausible causes and did not get a root cause I could defend.
Then I found the second bug, which was simpler and worse.
The Google handler did not navigate after success.
Apple login passed an explicit success callback that replaced the login route with the signed-in destination. Google login did not. The app had a session gate at its root, but the user was no longer standing on the root route. A session change on the login page did not move them anywhere.
That meant even a perfect OAuth exchange could leave the user looking at the same login screen. Success and failure were visually indistinguishable.
The feature was not flaky. It had never been complete.
Why Apple Review Did Not Catch It
Apple did not certify that every path through the app worked. It reviewed the app through the path it used.
We had supplied an email-and-password test account. The reviewer used it. That account exposed the subscription screens and the rest of the signed-in product, which were the focus of the review. There was no reason for the reviewer to test Google as well.
The four review rounds had caught real issues: missing subscription disclosure, account deletion requirements, age-rating metadata, and even the word "Free" in a store screenshot. The process did what it is built to do. It checked compliance and the reviewer's usable path through the product.
I had quietly assigned it another job: end-to-end authentication coverage.
That job was never Apple's.
Why the Green Checks Did Not Catch It Either
The app built successfully because the code compiled.
The automated tests passed because authentication was mocked. They could verify what the app did after receiving a simulated session. They did not ask Google for a real identity token, cross a browser boundary, return through a production deep link, or prove that Supabase accepted the provider configuration.
TestFlight distribution worked because distribution and login are separate systems.
The provider dashboards looked correct because configuration screens are descriptions of intended behavior, not observations of user behavior.
Every green check was truthful. I had asked each one to prove more than it proved.
I Removed the Chain Instead of Repairing It
After several failed theories about the browser flow, I stopped trying to save it.
Apple login had not shown the same class of problem because it was native. The system returned an identity token directly to the app, and the app passed that token to Supabase. There was no browser session to cancel, no deep link to lose, and no URL fragment to parse.
I moved Google to the same shape: native Google Sign-In obtains the ID token, then `signInWithIdToken` establishes the Supabase session.
That change introduced its own implementation work. The native module had to be loaded carefully so Expo Go would not crash when the module was absent. Two iOS dependencies needed narrowly scoped modular headers. Google's token included a nonce that the React Native library did not expose a way to supply, so Supabase's documented iOS-compatible nonce setting had to match the actual token flow.
Those details mattered, but the architectural decision mattered more. The fix was not a more elaborate set of guards around the browser chain. It was deleting several handoffs from the critical path.
Once the native version was in TestFlight, I used a physical iPhone to test both Google signup and Google login. I confirmed that an existing account remained the same account, with its existing characters and conversations. I signed out and checked that the native Google session was cleared too; otherwise the next tap would silently sign back into the same account and make logout look broken.
Only then was the button working.
The Release Rule This Created
The permanent rule is painfully literal:
Before an iOS release, tap Apple, Google, and email/password on a physical device.
For each one:
Why send a message? Because a login screen disappearing proves only that navigation happened. The first authenticated action proves the token reaches the backend, the account can access its data, and the product the user came for is actually available.
This is not a substitute for automated tests. It covers the thin set of boundaries automation commonly replaces with mocks: provider UI, native configuration, redirect behavior, release credentials, session persistence, and the production backend.
It takes minutes. The broken button was in the store for a day.
A Button Is a Promise
Authentication failures are unusually expensive because they happen before a user has any reason to forgive you.
Someone who has already spent weeks in a product may report a broken setting. Someone meeting it for the first time taps a login button, returns to the same screen, and leaves. There is no support ticket because there is no relationship yet.
That is why the absence of complaints meant nothing. A silent login failure removes the people who could have complained.
The uncomfortable lesson was not that App Review missed a bug. It was that I had mistaken several kinds of indirect evidence for one direct observation.
The build passed. The tests passed. The configuration looked right. Apple approved the app.
None of those events involved a real user tapping that specific button on a real phone and reaching the product.
Now every release does.
Ready to meet your AI companion?
Four unique personalities. Each one remembers you. Free to start.
Meet Your MatchKeep Reading
What AI Companion Memory Actually Does — and Why It Can Still Feel Forgetful
AI companion memory is more than storing facts, but less than perfect human recall. Here is how recent context, structured memory, character writing, and judgment work together.
How to Write an AI Character Backstory That Changes the Conversation
Most AI character backstories are lists of facts. Learn how to write specific causes, contradictions, relationships, and behavioral details that actually shape an AI character's replies.
Why Your AI Companion Should Disagree With You Sometimes
AI companions are very good at making users feel understood. New research suggests constant agreement has a cost. Here is why a believable character needs judgment, boundaries, and a way to say no.