Troubleshooting
Common symptoms in the click/redirect pipeline, their real cause, and where to look in code.
CTR/visits look inflated, extra unique visits every day
Symptom: Daily visit counts include traffic that doesn't match any real campaign or channel — a steady background trickle that inflates every domain roughly the same amount.
Cause: Synthetic self-traffic — local health-checks and watchers — hitting the funnel from a loopback subnet, which used to count as a real visit.
Check: the click buffer rejects any subnet starting with 127. before it's ever recorded. If
this regresses, the guard lives in record() (backend/app/products/link/core/click/log.py:156-166).
Non-owner tenants' clicks never age out under retention
Symptom: A multi-page/multi-tenant account's clicks table keeps growing past the retention window for every tenant except the main one.
Cause: The retention prune used to scope its delete to the empty-string owner only, so any
tenant with a real owner_id was silently skipped every day.
Check: prune() must delete on a clean Click.ts < cutoff with
execution_options(skip_tenant=True) — no owner filter at all — so the cutoff reaches every
tenant's rows (backend/app/products/link/core/click/log.py:391-411).
/r/paid or /r/ftl suddenly leads to a different offer than before
Symptom: A smart-link address that used to always mean "the paid offer" (or "the trial offer") now resolves to something else.
Cause: These addresses are aliases, not a fixed inventory slot — they're handed off to the domain's own smart-link policy (Route Spine) like any other slug, so they follow whatever that policy currently serves rather than a frozen offer.
Check: a hit on either legacy address logs a WARNING-level line naming the host and what it
was actually served, so you can see who's still pointing at the old address
(backend/app/api/of_redirect.py:1094-1132).
One visit shows up as two different click ids in the click report
Symptom: The same visitor's journey through a button and then a downstream /r redirect
produces two separate rows with two different click ids instead of one coherent journey.
Cause: Without carry-through, the button click (/api/go) and the smart-link redirect it
hands off to (/r/...) each mint their own click id independently.
Check: confirm the outbound URL to /r carries ?cid=<the button's click id> (via
outbound_cid.append_r_carry), and that /r accepts that incoming cid instead of always
minting a fresh one (_sanitize_cid, backend/app/api/of_redirect.py:230-246).
A moderator bot's clicks show up in the audit but never move the funnel/fraud counters
Symptom: A known moderation/scanner bot's clicks are visible in the raw click log but don't appear to move any conversion, funnel, or fraud total.
Cause: By design — every cloaked bot click is logged (is_bot = 1) for auditability, but it's
excluded from every count that matters via one shared predicate.
Check: any aggregation over clicks must filter with human_clicks()
(backend/app/core/click/filters.py:22-29). If a bot's clicks ARE moving a count somewhere, look
for a query that's missing .where(human_clicks()).
What's next
Back to Traffic, clicks & redirect for the tasks these symptoms show up in.