Daeseon Yoo
Back to project
·Tech retro·4 min

Build-passes ≠ works: a 26-finding adversarial audit of the mobile app, and the bugs a green build hid

The Expo app's eleven screens all compiled and bundled but had never run on a device. A six-dimension, adversarially-verified audit found 26 real issues — including a hard launch blocker (no signup screen) and a core feature that was silently dead (the shadowing loop never looped). 24 were fixed.

I built the mobile app screen by screen, and every one passed tsc and a Metro bundle. That felt like progress. It wasn't proof of anything — I had never actually run it, because the simulator is on a Mac I wasn't driving. "It compiles" is the floor, not the bar.

So instead of guessing, I ran an audit the way you'd review a stranger's PR you can't execute: fan out readers across explicit failure dimensions, and have a second, skeptical pass try to refute each finding against the actual code.

How it was run

Six dimensions, each a separate reader: API contract (mobile calls vs. the real backend controllers), state/hooks (TanStack Query invalidation, hook rules), React-Native/Expo runtime pitfalls, auth/session robustness, web-parity gaps, and launch readiness. Every finding then went to an adversarial verifier instructed to default to "not real" unless it could confirm the claim by opening the file. 21 raw findings survived to 20 confirmed. Then a completeness critic asked the question the six dimensions hadn't — what did we miss? — and caught the worst one.

What a green build hid

The findings that matter aren't the ones a compiler could ever catch:

And a tail of quieter-but-real ones: no global 401 handler (an expired token left you stuck on an error screen forever), the query cache not cleared on sign-out (the next user on the device briefly saw the previous user's data), a cold-start deep link bouncing logged-in users to login because it rendered before the stored token loaded, missing cache invalidation after grading and importing, the iOS audio session left in record mode so played-back takes went to the earpiece, and the Review screen getting stuck on "Review done" because its position never reset.

The fix, and the honest leftovers

24 of the 26 are fixed (dd0adfe), verified the only ways I can without a device: tsc clean, expo-doctor 21/21, Metro bundle green. Two are deliberately deferred: full Korean localization (the app is English-only despite a Korean user base — a real gap, but a large, separate effort) and multi-sentence clip range selection (single-sentence works).

The lesson I'm keeping: a passing build tells you the code is well-formed, not that the product works. Half of these would have shipped — a broken loop, a release pointing at localhost, an app with no way to sign up — and none of them are type errors. When you can't click through the thing, an adversarially-verified audit across named failure modes is the substitute, and its real leverage is the parts that aren't the happy path: the verifier that kills plausible-but-wrong findings, and the critic that names what everyone else skipped.