Skip to content

Troubleshooting

Find your symptom, then its fix. Each message RoutingCat prints is explained in the messages reference, and npx routingcat doctor, read check by check, names most of them.

init stops

init says Do this
No project it recognizes Run init in your site’s own folder, where its configuration is. Plain HTML pages are recognized by public/index.html, or by index.html at the folder’s root: move pages that sit in another folder, such as www/, into public/.
Several sites below the folder Run init in the folder of the site to set up.
The pages sit at the project’s root Move them into public/, which init recognizes by public/index.html, so that your host never publishes the build’s credential, then run init again from the project’s folder.
Nuxt 2, built by a server Set target: 'static' in nuxt.config and build with nuxt generate, then run init again.
It can’t tell where the site deploys or is served Run init again with --host vercel or --host cloudflare-workers, which is a Worker in front of your server where it renders your site, or, for a static site, --host cloudflare-pages. Where neither Vercel nor Cloudflare serves it, --host none sets up the script integration, which the docs for any other website cover.
Laravel, Rails, Django, WordPress or PHP set up for the script integration Where Vercel serves your site, or a Worker in front of your server, run init again with --host vercel or --host cloudflare-workers: it moves the site there, its pages, variants and results kept.
--host refused Pages serves static sites only: a site your server renders takes --host vercel or --host cloudflare-workers. A site set up on Workers or Pages moves to Vercel or to the script integration, never to the other.
The folder isn’t linked to Vercel, or the Vercel CLI is signed out Run npx vercel link or npx vercel login, then init again.
Wrangler is signed out, or reaches several accounts Run npx wrangler login, or set account_id in the Wrangler configuration, then init again.
It needs your production address Run init again with --origin and the address, as --origin https://www.lumen.com.
Workers Cache is on Remove cache from the Wrangler configuration: see Cloudflare.
Installing the package failed Install routingcat yourself, then run init again.
The sign-in isn’t approved yet, with exit code 3 Approve the link, then run init again: it continues where it stopped.
Your host’s project belongs to a site you’re not a member of Ask that site’s owner to add you.
A file is too unusual to edit init prints the exact edit, such as the head partial’s include: make it, then run npx routingcat doctor, which checks it.

Running init again is safe: it does what’s missing, brings RoutingCat’s own entries up to date, and leaves your edits as they are.

push or the build refuses your markup

push, plan and the build check your templates alike, and name the file, the line and the fix, here for a variant’s key written as a Hugo expression:

$ routingcat push
✗ layouts/index.html:6: data-rc-variant is written as a template expression, {{ .Params.hero }}, so routingcat cannot read this slot from your templates. Write the key itself: lowercase letters, digits, - and _. (D66, exit 1)
The message says Do this
Stamps or the head partial out of date Run npx routingcat push, then commit what it changes. The build fails until you do.
Markup the scan can’t read Write each key as itself, never as a template expression; close every tag inside the variant that opens it, in the same branch of a conditional; keep slots in HTML-syntax templates.
Markup that breaks its grammar One default, without hidden; every other variant hidden, with no display in its style; every image lazy; every copy of a slot alike.
A slot the browser would move Use span elements inside a paragraph, put the container in a table cell or a list item, and never nest a slot in another’s variant or in <head>.
A variant holding what acts while hidden Move scripts, structured data and stylesheets out of the variant, give each id once, keep one h1 at most, and keep forms where they work once shown.
The markup and routingcat.config.json disagree Declare under pages each page your markup names, with its routes, and name slots and variants as the markup does.
Built pages that differ from the templates Build from the templates push stamped, keeping the slot markup as written: a minifier that drops data-rc-* attributes or hidden breaks the slots.
A routed page without an objective Add the line it prints to routingcat.config.json.
A config field outside its rules Correct the field it names.

Build a page gives every rule with its reason.

The build registers nothing

A build that registered prints RoutingCat’s receipt in its log, starting routingcat LIVE, NOT LIVE or registered. Without it, the deployment shows your defaults and records nothing, conversions included. The build log says why, on a line starting routingcat:.

A build with no token and no deploy key says what to set where it runs. In Vercel’s build:

routingcat: no Vercel or GitHub Actions OIDC token and no ROUTINGCAT_DEPLOY_KEY, so this build cannot identify itself to RoutingCat: it registers nothing, and visitors get your defaults. On Vercel, turn on Settings → Security → Secure backend access with OIDC federation, then redeploy.

In Cloudflare’s builds:

routingcat: no Vercel or GitHub Actions OIDC token and no ROUTINGCAT_DEPLOY_KEY, so this build cannot identify itself to RoutingCat: it registers nothing, and visitors get your defaults. Run npx routingcat keys deploy --branch <your production branch>, set what it prints as the build variable ROUTINGCAT_DEPLOY_KEY in Cloudflare's build settings, then redeploy.

In any other CI, the line names what that CI takes. The build log’s other reasons:

The build log says Do this
RoutingCat can’t be reached Redeploy once it’s back.
The build’s token or deploy key was refused Check ROUTINGCAT_DEPLOY_KEY. A production key proves only builds of its branch, as your CI reports it, and in a CI RoutingCat doesn’t read, as ROUTINGCAT_BRANCH names it: see Cloudflare. On your machine, for a site on Cloudflare, run the build with ROUTINGCAT_ENV=production instead: see Deploy from your machine. On Vercel, check that the project is still bound to your site.
The Vercel project is bound to another site Make site in routingcat.config.json the project’s site.
A build on your own machine Nothing, for a build that tries your pages under wrangler dev or vercel dev. To deploy production to Cloudflare from your machine, run routingcat build with ROUTINGCAT_ENV=production: see Deploy from your machine.

When the log holds no line of RoutingCat’s at all, your build command doesn’t run npx routingcat build: Set up RoutingCat gives it for each host.

Once a production build registers, its deployment registers at its first request. If none has 10 minutes later, open your site; if it still doesn’t register, check your host’s entry, as init wrote it: middleware.ts exporting middleware(…) from routingcat/vercel, a Worker whose main exports worker(…) from routingcat/cloudflare, or functions/_middleware.ts exporting onRequest = pages(…).

Pages show no decisions

A decision is RoutingCat’s choice of a visitor’s variants on a routed page, made by your host’s middleware or Worker. doctor checks each page’s first one.

  • RoutingCat didn’t run on the page (the middleware or Worker did not run). On Vercel, middleware.ts keeps RoutingCat’s matcher entries. On Cloudflare, run_worker_first keeps RoutingCat’s patterns, or the Worker’s route covers your pages. doctor names the page, here on Workers:

    ✗ Worker on / https://www.lumen.com/ answered without RoutingCat's visitor cookie, rc_vid, so the Worker did not run on /: visitors there get your defaults, and their visits are not recorded. The Worker's main must export worker(…) from routingcat/cloudflare, and run_worker_first must keep RoutingCat's patterns. (D82)
  • Vercel runs the middleware on another runtime (not Node.js). Keep runtime: 'nodejs' in middleware.ts’s config, then redeploy.

  • The page came over plain HTTP (plain HTTP). RoutingCat leaves such views undecided: turn on “Always Use HTTPS” in Cloudflare.

  • The page isn’t live yet. Only a production deploy makes a page live, and for about 2 minutes after its first one on Vercel, the page shows its defaults and records arrivals only.

  • Routing is off. The build log’s receipt says so; npx routingcat set routing=on turns it back on. When your plan turned it off, past Hobby’s visitors a second month running or at Pro’s cap, upgrade or raise the cap instead.

  • Routing waits for a card. On Hobby, it starts once the site’s owner has a card on file: see Routing waits for a card.

  • A slot has one variant. Nothing is routed there until you add a second.

  • A cache in front serves the page (a cache in front). Stop caching routed pages’ HTML there: Run it in production.

  • The middleware or Worker lost access (no records). Redeploy, which mints a fresh credential. On Cloudflare’s Workers Free plan, the Worker also stops past 100,000 requests a day: RoutingCat needs Workers Paid.

  • Decisions come, but no render beacon. Keep the tracker’s script of RoutingCat’s head partial in each page’s <head>, and let your Content Security Policy load it, by the page’s nonce on its tag or with https://cdn.routingcat.com in script-src, and send its beacons through /_rc, with 'self' in connect-src. In your own browser, an ad blocker also stops it.

Visitors routed to a variant see the default

Every variant is in the page, and RoutingCat’s head snippet shows the routed one before the page paints. When the default shows instead:

  • The head snippet is missing from the page (no head snippet). Include the head partial first in the <head> of the layout that renders it; on plain HTML, paste it in the page; on Nuxt 2, keep RoutingCat’s entries in nuxt.config.js’s head, with the snippet’s hid in __dangerouslyDisableSanitizersByTagID. doctor names the page and the partial, here on Vercel:

    ✗ head snippet https://www.lumen.com/ carries no RoutingCat head snippet in its <head>, so visitors routed there see your defaults. Include layouts/partials/routingcat-head.html first in the <head> of the layout that renders /. (D89)
  • Server-Timing doesn’t reach the page (no rc entry). The selection travels in it: remove the header rule or the proxy in front that drops it.

  • The page’s Content Security Policy blocks the snippet or its style rules (the policy). Give the head partial’s script tags the page’s nonce in your template: push keeps it, and the style rules the snippet writes carry it too. Or allow the snippet by its hash in script-src, 'sha256-VA7FIBNQntYn209pbrfrTBJ1g1fF+qHfG+08SyvP+D8=', which an upgrade of routingcat can change; on Hugo, whose build rewrites the snippet, by the hash doctor names, which changes with your build. With the hash, allow the style rules with 'unsafe-inline' in style-src, and no hash or nonce beside it there, which would cancel it: the rules differ from one visitor to the next.

  • Rocket Loader runs the snippet late (Rocket Loader). Keep data-cfasync="false" on its tag, as RoutingCat writes it.

  • Behind a Worker in front of your server, the Worker didn’t register the page’s markup (unregistered markup). Until the Worker’s build and your server’s deploy serve the same templates, a slot whose markup changed shows its default and records no decision: deploy the Worker with your server’s templates.

  • The page’s markup differs from your templates (served markup), as when a minifier drops data-rc-* attributes or hidden. Deploy what push stamped, the markup as written.

  • The browser is too old to show a selection: Safari before 16.4, Chrome before 105 and Firefox before 121 get the defaults, unrouted.

When slots show as boxes instead, the policy refuses the markup’s style="display:contents". 'unsafe-inline' in style-src allows it; a style-src-attr of your own takes 'unsafe-hashes' and the hash doctor names, 'sha256-BDgIqUOjni0WelhOfIRW4cmSnxZNAa0L9tCq85UnRac=' for the value as RoutingCat writes it.

Routing waits for a card

On Hobby, routing starts once the site’s owner has a card on file, and nothing is charged. Until then, every visitor sees your defaults, and:

The owner adds a card in the dashboard’s Settings → Billing, with nothing charged, or starts Pro’s 14-day trial. Routing starts within about 2 minutes, with no new deploy. A member asks the owner. See Add a card to start routing.

A variant never shows

  • Your browser keeps its variants for the whole visit, 7 days by default, and may be in the control, the 10% who always see the defaults. To see a variant, add ?rc=hero:learns under wrangler dev or vercel dev, after npx routingcat build, or use a preview link in production: Change, preview and deploy.
  • It’s paused or retired. npx routingcat show home/hero:learns says so, and npx routingcat resume home/hero:learns brings it back.
  • The plan holds it. On Hobby, past the 10 live variants across the owner’s sites, a variant is registered and not served: see A variant is held.
  • It’s new, or routing favors others. A new variant starts on a limited share, and routing serves most what is likely best, though the random slice shows every variant now and then. show gives its share.
  • It was added during a visit. A slot added mid-visit shows its default to that visitor until their next visit.
  • Its build failed after registering (a build that failed after registering). Visitors keep the previous version: fix the build and redeploy, or revert the page.
  • Browsers report it shown less often than the slot’s other variants (for a smaller share of the page loads it was picked for). Check that it renders as fast as they do.

A variant is held

On Hobby, the sites you own serve at most 10 live variants together, the slots’ defaults not counted. A deploy or an act past them still succeeds, and each variant past the 10th is held: registered, and not served. The build log names it after its first line, status reads held on its row, and the dashboard marks it “Held by the plan”.

To put it live, either:

  • upgrade to Pro in Settings → Billing, for every site you own, which allows up to 100 live variants a page;
  • or retire or pause a live variant on any of your sites, which frees room.

Held variants then go live in the order they were declared, within about 2 minutes, with no new deploy. See Go past Hobby’s 10 live variants.

Conversions don’t join

Track conversions explains the tracking line of status. The messages behind it:

The control’s share is off

status raises the control check when the control’s share of new visitors departs from its setting, which can bias the measured gain. Check that page for a cache in front, or a matcher of RoutingCat’s middleware or Worker that skips it for some visitors.

What happens Do this
preview says the production deployment doesn’t carry the version Run push, commit, and deploy it to production first, the Worker too where one stands in front of your server.
preview says no deployment serving it has registered yet Right after a deploy, open your site, then run preview again. If the build failed after registering, deploy again.
preview says the page has no path to preview Pass one: --path /pricing.
preview refuses variants of several pages, or several of one slot Preview one page at a time, one variant per slot.
The page opens without the preview badge The link is valid for one hour: make a new one. The badge comes from the tracker’s script in the head partial, so keep it there.
The badge names another variant The badge reads what the page shows. Right after a deploy, a new instance can serve the page before it knows the variant: reload a few seconds later.

To end a preview before its hour is up, follow the badge’s link, or open the page with ?rc_preview=off at the end of its address.

Run commands in a sandbox

Coding agents often run commands in a sandbox, as Work with your coding agent explains:

  • No network, exit code 4: allow network access for the command, or ask the person to run it.
  • The sandbox can’t keep a sign-in, exit code 3: run npx routingcat login in your own terminal, then the command again.
  • A sign-in link is waiting, exit code 3: give the link to the person, and once they approve, run the same command again.
  • A production build on the machine waits for the person, exit code 3: show them the plan it printed, with any warning beside it, and once they agree, run the build again with ROUTINGCAT_CONFIRM set to its digest, beside ROUTINGCAT_ENV=production.
  • doctor --wait runs up to 5 minutes: give it a 6-minute timeout, or run it in the background.

push, push --check and push --dry-run read and write your templates only, with no sign-in.

Read doctor check by check

npx routingcat doctor reads your project, your sign-in, your site’s records and your production pages: /, and each page’s url in routingcat.config.json. It changes nothing. Its requests are recorded as arrivals, flagged as RoutingCat’s own and kept out of results. After a deploy, --wait waits up to 5 minutes, printing each check as it passes, and stops early when only a person can move the rest. Set up RoutingCat shows a run where every check passes.

Check Passes when Otherwise
Your files, shown only when one fails init‘s edits, your templates’ stamps and head partial, the site’s public key in routingcat.config.json, and an up-to-date skill an edit to make, stale stamps or partial, the wrong public key, the skill
build registered A production build registered, with the objective and outcomes it declares Waits for a production deploy
deployment registered The deployment serving that build registered at its first request No deployment registered after 10 minutes
runtime, on Vercel Vercel runs the middleware on Node.js Another runtime
Worker version, on Workers The deployment registered under its Worker’s version No version metadata binding
middleware on /, or Worker on / Your production page set rc_vid RoutingCat did not run, or a cache in front answered. On Cloudflare, it waits until routingcat.config.json lists your production address: run npx routingcat init --origin with it.
/_rc Your site’s /_rc path reaches RoutingCat Browser events skip /_rc
head snippet Each page carries RoutingCat’s head snippet The snippet missing
selection Each routed page’s answer carries its selection in Server-Timing The selection missing, RoutingCat did not run, a cache in front, or a build made for development on a person’s machine
policy Your pages’ Content Security Policy lets RoutingCat run, apply and record What it blocks
Cloudflare’s features No zone feature changes what your pages run Rocket Loader, Zaraz before the snippet, or plain HTTP
records RoutingCat accepts records from your production address Records refused
markup Each page’s slots, as served, match your templates, and behind a Worker in front of your server, the markup its deployment registered Markup that differs, markup the Worker didn’t register
first arrival The pulse shows an arrival, often the check’s own request Waits about a minute
The objective, by its name A first conversion arrived No event yet: waits for the first real conversion, a visitor’s or your own, and nothing else waits on it
card on file, on Hobby The site’s owner has a card on file, shown by its brand and last 4 digits Waits for the owner to add one, at the link it gives: routing waits for a card
page live, first decision, first render beacon Each routed page is live, decided, and shown in a browser Waits for a visit; no render beacon, or a cache in front
server key, location headers, shown only when one fails The preview key used in production, Cloudflare in front of Vercel without its location headers

Lines marked · are information and need nothing: conversions with no routed visit yet, an undeclared name, and, run in your project, whether AGENTS.md has the note for coding agents, which is your choice.

doctor exits with 0 when every check passed, 1 when one failed, 3 when one is still waiting, and 4 when the network blocks it.

Next