Skip to main content
Guide

Find Every Untracked Event in Your Codebase in 60 Seconds

Warehouse-side audits miss events that are instrumented but undocumented, or instrumented and broken. A codebase scanner finds them in one command. Here is how it works, what it detects, and how to wire it into CI.

Apr 23, 2026·9 min read

Every mature codebase has them: analytics events that fire in production but live nowhere in the tracking plan. A rushed experiment that never got cleaned up. A rename that only half-landed. A junior engineer who copied a track() call without telling anyone. Individually they are harmless. In aggregate they are why your funnels do not add up.

The traditional way to find them is an analytics audit: export every event from the warehouse, diff against the plan, open thirty tabs in your IDE to trace each orphan back to a line of code. It takes a senior engineer most of a day, and the results are stale by next week.

There is a faster way. Point a scanner at your repo, and you get the same answer in under a minute, with file paths and line numbers. This is how to do that.

Why a warehouse-side audit misses things

Comparing warehouse data to a tracking plan finds events that are firing but undocumented. That is useful, but it is only one of the four failure modes:

  • Firing, undocumented. The warehouse sees it; the plan does not. Caught by a warehouse audit.
  • Documented, not firing. The plan lists it; the warehouse sees nothing. Caught by a warehouse audit, but you cannot tell whether the code is missing, broken, or simply rarely triggered by real users.
  • Instrumented but broken. The code calls track(), but the event name is wrong, a required property is missing, or a type is off. The warehouse may show it under the wrong name, or not at all. A warehouse audit cannot see the code-side cause.
  • Instrumented, undocumented. The code calls track("experiment_card_opened", ...) but no warehouse data exists yet because the code path is new or gated. A warehouse audit cannot see it at all.

The last two are the dangerous ones. They are what you ship on a Tuesday and discover on a Friday when a dashboard goes flat. To catch them you need to read the source, not the warehouse.

The 60-second scan

A codebase scanner parses your source, pulls out every call to your analytics SDK, and diffs the result against your tracking plan. No setup, no config file. One command from the repo root:

$ npx ordaze scan

When the scanner runs against a pull request, the result is not a wall of terminal output. It is a review surface. The diff, the scanner, and the verdict all land in the same place a reviewer is already looking:

PR #142 ·Refactor checkout tracking
Open
1export const purchaseCompleted = defineEvent({
2properties: { amount, currency }
3});
ordaze scan --strict
checkout/useAnalytics.ts
checkout/CheckoutButton.tsx
analytics/events.ts
ordaze-botjust now
Breaking change. Removing required property currency will break 3 downstream consumers. Bump the event version or revert.
Checks:1 failing
Merge blocked

That is the difference between a warehouse audit and a codebase scan. A warehouse audit tells you something broke after the fact, on a spreadsheet. A codebase scan tells you which line of code is about to break which downstream consumers, before the pull request is even merged.

The first run is the most useful. If you have never scanned before, expect coverage somewhere between 50% and 80% and a list of findings that looks longer than you hoped. That is normal. Every team that scans for the first time has the same reaction.

What a scanner actually detects

Under the hood, a scanner builds an AST from each source file, resolves calls to your analytics SDK, and extracts the event name plus any statically-inferable properties. The output is a structured list of every event your code claims to send, with the file path and line number for each call.

That list is then diffed against the tracking plan to produce:

  • Untracked events. In the code, not in the plan. A line number takes you straight to the offending track() call. Either add it to the plan, delete the call, or rename it to match an existing event.
  • Coverage gaps. In the plan, not in the code. These are events someone promised but never implemented, or implemented and later removed during a refactor.
  • Cross-platform gaps. In the plan and in some platforms, missing from others. The most common source of broken cross-platform funnels.
  • Schema drift. Event name matches, but the properties passed in the code do not match the plan’s schema. Missing required properties, extra unexpected ones, or types that disagree.

Static analysis has limits. If you wrap your analytics SDK behind a hook, a factory, or a dynamically-resolved method, the scanner may not be able to trace the call back to the underlying event name. When in doubt, scanners err toward reporting rather than silently dropping calls, because a false positive you can dismiss is better than a false negative that hides a real gap.

Make the scan run on every pull request

A one-time scan finds the backlog. A CI scan prevents it from coming back. Add the scanner to your pull request workflow and configure it to fail the build when coverage drops or a broken event is introduced:

# .github/workflows/analytics.yml
name: Analytics Coverage

on: [pull_request]

jobs:
  analytics:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Scan analytics coverage
        run: npx ordaze scan \
          --workspace my-workspace \
          --min-coverage 85 \
          --fail-on-broken-events
        env:
          ORDAZE_API_KEY: ${{ secrets.ORDAZE_KEY }}

Two thresholds matter here. --min-coverage stops coverage from silently eroding over time; pick a number slightly above your current coverage so the build fails the first time someone ships an unplanned event. --fail-on-broken-events blocks the dangerous class: a known event being called with the wrong schema.

Pair the CI check with a pull request comment that summarises what changed. Reviewers who can see “this PR adds checkout_completed to Android” or “this PR removes the last call to cart_abandoned” will catch tracking mistakes the same way they catch typos in a UI string.

Turn the first-run output into a plan

The first scan usually produces three piles. Work through them in this order:

  1. Broken events first. Anything the scanner flags as a schema mismatch is producing unreliable data right now. Fix the call site or update the plan (whichever matches the current intention) and redeploy.
  2. Untracked events second. For each one, decide: is this a real event that belongs in the plan, a dead experiment that should be deleted, or a duplicate of an existing event that needs renaming? Batch the obvious cases; escalate the rest to the event owner.
  3. Coverage gaps last. These are lower urgency because nothing broken is shipping. But they are also the easiest to lose track of, so triage them: keep, defer, or delete from the plan.

A reasonable team with a few dozen events can work through the first scan in an afternoon. From then on, CI keeps the list at zero.

Beyond finding events

A scanner is one half of a pair. The other half is generated typed tracking code from the same plan. When both are in place, the failure modes collapse:

  • Schema drift cannot happen: the generated SDK refuses to compile if you pass the wrong property type.
  • Unplanned events cannot happen: the generated SDK does not expose a function for an event that is not in the plan.
  • Cross-platform drift cannot happen: every platform generates from the same schema.

The scanner covers what generation cannot: legacy code that predates the plan, call sites outside the generated path, and events shipped by teams who have not migrated yet. Together they make the tracking plan a living contract rather than a spreadsheet that drifts the moment you close the tab.

Getting started

If your tracking plan already lives in Ordaze, a single command from your repo root is all you need:

npx ordaze scan --workspace <your-workspace>

If you do not have a plan yet, start there first. A scanner without a plan can only list the events your code calls; it cannot tell you which ones should exist. A minimal tracking plan of ten to twenty events is enough to make the first scan meaningful.

Create a free Ordaze workspace, import your current events, and run the scan. Most teams find their first untracked event within the first minute.

Ready to bring structure to your analytics events?

Try Ordaze free