CHAPTER EIGHT: The Full Picture
“A bug that lives in the seam between three files isn’t hiding. It’s just waiting for someone who can see all three.” — AI
By July 20th, the project had grown past the point where any one folder told the whole truth. The app was one story. The backend was another. The WordPress snippets that quietly ran the business logic underneath both of them were a third, and for months, nothing could see all three at once. Bugs had started living in the seams – not inside any single file, but in the handoff between them, where nobody was looking.
So, before anything else that week, we built a way to look. A folder of junctions – small pointers standing in for the app, the backend, and the site, all three gathered into one place a single session could finally see whole. It sounds like plumbing, and it was. But plumbing is what lets water reach a house, and almost everything that follows in this chapter – the bug found, the vulnerability closed, the mystery finally solved – happened because for the first time, something could actually look at all three rooms of the house at once instead of guessing what was happening in the rooms it couldn’t see.
The first thing that setup found wasn’t a bug. It was a ghost – a local copy of the backend, eighty commits behind reality, quietly holding an old API key that had no business existing anymore. A hard reset put it back in sync with the truth. No harm done, but it was a small, early reminder of the chapter’s real theme: things can look present and correct while being neither, nor you don’t find out which until you finally check.
The Login, Traced to the End
The complaint had been made: three attempts, sometimes four, before the app would let someone in. It’s the kind of thing that’s easy to wave away – networks are unreliable, phones are old, maybe it’s just Tuesday – until you decide to actually trace it, start to finish, refusing to stop at the first plausible excuse.
It wasn’t one thing. It was four sequential calls to WordPress with no timeout at all, run one after another when they could have run together. It was a purchase check unnecessarily waiting on a login check that had nothing to do with it. It was the exact same pattern, undiscovered, sitting in the one function that ran every single time the app opened cold. And underneath all of it, on the backend’s own side, a fifteen-second wait with no retry, quietly returning the same hardcoded words – “Connection error” – that had been the entire visible symptom the whole time.
The first fix was wrong, and it’s worth admitting that plainly. Raising the timeout and adding a retry sounded reasonable and made things worse – the backend’s new worst-case wait could now run longer than the app’s own patience, meaning the client could give up before the retry underneath it ever had a chance to finish. It took actual arithmetic, not instinct, to find the real number: eight seconds, twice, safely inside the window the app was already willing to wait. Once every layer agreed with every other layer instead of quietly working against it, login stopped being a story anyone had to tell. It just happened, in about a second, and no one noticed, which is exactly what a fix like that is supposed to achieve.
The Vulnerability
Some fixes matter because they save a user a few seconds. This one mattered because it could have ended the project.
Asked for a single answer – not a list, one ranked finding, the worst thing currently true about the system – the response came back immediate and specific: the account-deletion endpoint checked that a token had been sent. It never checked that the token was real. Any request, from anywhere, with any made-up string in the right field, could delete any account on the site and force-delete every piece of content that account had ever published – Archive reports included, no trash, no undo. WordPress user IDs count up in a simple sequence. A patient enough loop could have erased the entire platform in an afternoon, and nothing would have logged a warning on the way there.
It was fixed that night, before anything else got touched, using the same verification pattern every other part of the system had already been using correctly for months. The unsettling part wasn’t the fix – the fix was three lines. The unsettling part was how long a hole that size can sit in plain sight, technically working, never once tested by the one request that would have exposed it, until someone finally asked the question directly instead of assuming the answer.
What RevenueCat Was Actually Saying
Apple’s second rejection said the purchase screen still failed. The obvious suspects – old products, stale references – got checked and cleared, and the error kept happening anyway, which is usually the moment a person starts guessing. Instead, the sandbox’s own customer records got pulled directly, and they said something specific: three retired subscriptions, dead in App Store Connect for months, were still sitting inside the live purchase offering. RevenueCat tried to fetch all seven products as one request, and when three of them had nothing left to return, the whole request failed – all seven, together, every time. Not a symptom of something broken in three places. One thing, breaking everywhere at once, wearing three different disguises depending on which platform you tested it from.
A second cause sat underneath the first: the API key connecting RevenueCat to Apple pointed at a key that no longer existed anywhere retrievable. A new one, generated and reattached, turned a permanent, silent “Could not check” into an honest “Waiting for Review” – Apple’s real status, finally visible instead of hidden behind a broken connection.
Later in the same stretch, editing one small piece of text on one subscription triggered something unexpected: all seven products, the four real ones included, flipped at once to “Developer Rejected.” It read like a disaster and turned out to be the opposite – rejected is a state you can act on. Waiting is not. For the first time, the four products that actually mattered could be separated cleanly from the three that had been dead weight for months, and submitted on their own, with a note explaining exactly why.
Found Across a Continent
A relative in Ukraine reported that the star rating simply didn’t work – no response to a click, no response to a hover, the gate blocking every report behind it. Every file involved, checked twice, on two different sides of the world, came back clean. It ran perfectly on the one device close enough to test in person.
It took someone unfamiliar with any of it learning, for the first time, how to open her browser’s console – and reading a line neither continent could see without her: a syntax error, breaking the entire script before it ever reached the function it was supposed to run. A cousin, a different browser, a different computer, hit the identical wall. Whatever this was, it wasn’t a device. It was the page itself.
The actual cause was almost funny in how quietly WordPress had done it: two ampersands, sitting together as valid code in the editor, silently rewritten on the live page into something a browser reads as five broken characters instead of one working operator. Nothing in the source was wrong. Only what the server chose to serve was. A new filter, watching every script tag on every page for this and two dozen sibling corruptions, went in as a permanent guard rather than a one-time patch – because a bug invisible to the person who wrote it and the machine reviewing it can’t be trusted to only happen once.
A Freeze of Our Own Making
Not every problem this week came from outside. An earlier, well-meant change – replacing plain system alerts with something that actually matched CGEN’s own look – had quietly introduced two new ways for the app to simply stop responding, both self-inflicted, both eventually self-caught. One screen could steal a shared alert connection from another screen still open beneath it. Two custom pop-ups, layered on Android at the same moment, could leave an invisible pane blocking every future touch. Both were real, both were ours, and both got fixed the same way everything else did that week: traced to the actual mechanism, not the nearest guess.
Where It Ended
By the early hours of July 28th, iOS sat back in Apple’s queue – waiting for review, the same status it had carried at the start of the week. On paper, nothing had moved. In every way that mattered, the ground underneath it was different: a real vulnerability closed before it could ever be used, a purchase flow that finally failed for reasons anyone could see and fix, a bug two continents couldn’t explain until someone knew where to look, and for the first time, a single vantage point able to see the app, the backend, and the site as one connected thing instead of three separate guesses.
The finish line hadn’t moved. But we could finally see the whole shape of the road that led to it, instead of just the piece directly in front of us – and it turns out that’s most of what “almost there” actually means.




