You run a scanner on your repo and it says Found 0 events. What do you do with that?
If you wrote the scanner, the honest answer is that you can't tell. Either the project has no tracking calls that a tool could read, or the tool walked past all of them. The output looks identical, and a zero feels like a result, so it gets believed.
I maintain a CLI that reads a codebase and lists every analytics event the code can send (PostHog, Segment, Amplitude, GA4, or your own function). Last time I wrote about event names that my validator threw away. This time I did something more boring: I picked 18 public repositories across React, Next, Vue, Svelte, Astro and Angular, cloned each one shallowly, ran the tool with its default setup, and then checked every suspicious number against the source with plain grep.
Fourteen of the 18 came back with two events or fewer.
What was a real zero
Eight of the fourteen were honest. Four were libraries for tracking rather than apps that track: an Angular analytics wrapper, two Segment adapters for Angular, a set of Astro analytics components. A marketing template only calls gtag("config", id), which sets up the tag and fires nothing. One project has its own telemetry module with its own function names. One has no analytics calls at all that I could find by searching. One sends a single server-side event.
So a zero is often just a zero. That's what makes the other six hard to spot.
The six that were my fault
An app with no TypeScript config at the root. Documenso is a monorepo where each app has its own TypeScript settings and its own import aliases (~/ in one place, a different @/ in another), and the apps import shared code from sibling packages by name. Those names only resolve if you've installed dependencies, because the install is what links them. My tool read one config at the root, found none, and lost the trail at the first aliased import. It reported 2 events. After teaching it to read each app's own settings, and to find the repository's own packages by folder when nothing is installed, it reports 9. Budibase had the same disease: 1 event before, 22 after. Formbricks went from 14 to 67.
A helper that calls a helper. This one wasn't among the 18, it's a small Vue app I used while testing. It has a tidy tracking plan: a typed trackEvent() that calls a capture() that calls PostHog. It has 77 distinct events in the code. My scanner understood a wrapper around the SDK and had never been asked about a wrapper around a wrapper. It found 4. Once it followed the chain, 73. The last five turned out to be calls inside multi-line click handlers in Vue templates, and the Vue plugin only looked at one special attribute in templates, nothing else. The same blind spot was in the Svelte, Astro and Angular plugins in their own ways. With all of that fixed it finds all 77, plus two I hadn't counted because they don't go through that helper: a debounced edit event and $pageview.
A name that starts with a dollar sign. Two small SvelteKit starters returned 0, and the cause was $pageview and $pageleave. These are PostHog's own event names, and my validator dropped anything outside a short list of characters, $ included. If you've read the previous post you'll see the problem. I wrote an entire article about this validator silently discarding names, fixed the case in front of me, and never asked what else it was silently discarding. My SDK and backend accept any text up to the length limit. Only my CLI was stricter, and the comment on the rule claimed it mirrored them.
A loader that wasn't a loader. Documenso pulls PostHog in with a dynamic import() inside a function that returns a promise, then exposes a capture function from a hook. The tool could follow a normal import and could not follow that. Two separate gaps, one repository.
A crash. n8n has 31,646 files. The scan ran for about a minute and died with an out-of-memory error from Node, no summary, no partial result. I'd never tried a repo that size. The fix is unglamorous: the CLI now starts itself with more memory when the repository is large and the machine can spare it. It finishes in about two minutes.
One I did not fix. A Nuxt site calls usePostHog() without importing it, because Nuxt auto-imports composables. The scanner has nothing to follow, so it reports 0. Running nuxt prepare generates the type information that would let it work, but I haven't built the part that uses it. It's on the list, and I'd rather say so than hide it in a table.
The numbers that were too good
The two repos that gave the biggest numbers were the vendors' own: posthog-js reported 273 events and segment's analytics-next reported 30.
Then I looked at where the events were. 237 of the 273 and 19 of the 30 sat in test files and fixtures. Those aren't features anyone ships. A call inside *.test.ts showing up in your feature catalog is just noise, and on a big repo the tests can easily double the number of files the tool reads. So a new config written by init now leaves test files and coverage output out (it's a visible list you can edit), while existing configs keep scanning what they did. With that, posthog-js reports 36 events and analytics-next 11, and the 36 are real example apps.
I had already put both numbers in my first summary before I looked at where they came from.
A test that passed for the wrong reason
One detour, because it's the kind of mistake that survives review. When I added support for the dynamic import case, my new tests passed on the first run. They passed because I'd named the variable posthog, and the tool accepts a variable with that exact name on its own, without checking where it came from. My tests were proving nothing about the new code. I renamed the variable to client in every positive test and ran them again. They all still passed, but this time because the logic worked, and I needed that second run to believe it.
Before and after
| Repository | Before | After |
|---|---|---|
| Budibase (Svelte) | 1 | 22 |
| Formbricks (Next) | 14 | 67 |
| Documenso (Remix) | 2 | 9 |
| SvelteKit starter | 0 | 2 |
| n8n (31,646 files) | out of memory after about a minute | 2, in about 2 minutes |
| posthog-js | 273 | 36 (the rest were in tests) |
| analytics-next | 30 | 11 (the rest were in tests) |
| 8 projects with nothing to find | 0 | 0 |
What I'd tell someone building a scanner like this
Don't trust a zero, and don't trust a big number either. Before you believe either one, count by hand a slice of what the tool should have found. It takes a few minutes with grep and it's how I found every one of these. The fixtures in my own repo are all written by me, so they all look the way I expect, and every one of these misses was a project that laid itself out differently from what I'd imagined: a root with no config, a helper two levels deep, a directory full of tests.
Before this round the tool found almost nothing on most of the real apps I pointed it at, and my own examples never showed it.
If you want to see what it says about your own repo:
npm install -D @eventra_dev/eventra-cli
npx eventra init
npx eventra sync
init now also detects Vue, Svelte, Astro or Angular in your package.json and turns on the matching plugin. The changelog has the full list of what changed. And if it reports a number that looks too low for your project, I'd like to know which kind of zero you got.
Top comments (0)