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 |
|---|---|
| It recognizes no project here | Run it in your site’s own folder, where your templates are. |
| Several sites are here | Run it in the folder of the site to set up. |
| It can’t tell where the site deploys or is served | Run it again with --host none, for a host that runs nothing of RoutingCat’s. |
| It needs your production address | Run it again with --origin https://www.example.com. |
| The sign-in isn’t approved yet, with exit code 3 | Approve the link, then run init again: it continues where it stopped. |
| A file is too unusual to edit, such as a layout among several | init prints the exact edit: make it, then run npx routingcat doctor, which checks it. |
| npx can’t run the version the config pins, with exit code 2 | Check your network, and the registry npm config get registry prints, then run the same command again; or move the site to the version you ran, as the message says. |
Running init again is always safe: it does what’s missing and never overwrites your edits.
A push registers nothing
A push that registered prints RoutingCat’s receipt last, starting routingcat LIVE, NOT LIVE or registered. Without it, a deploy that changed your templates leaves your pages on their defaults, recording no decision. The push says why:
| The push says | Do this |
|---|---|
| It registered nothing, with no credential here | On your machine, that is expected: commit what it changed, and right after your deploy, register production with npx routingcat push --env production, or in your CI or your host’s deploy script. In GitHub Actions, link the repository with npx routingcat link github and give the job permissions: id-token: write; in any CI or deploy script, set ROUTINGCAT_DEPLOY_KEY, from npx routingcat keys deploy --branch <production branch>. |
| Nothing was pushed: the plan waits for the person, with exit code 3 | Show the person the plan it printed. Once they agree, run it again with --confirm and the digest it gave. |
| The confirmed plan has changed since | Your templates or production changed: show the person the new plan, and confirm that one. |
| The credential was refused, with exit code 1 | The reason names it. A production deploy key proves production only for pushes of its branch, which RoutingCat reads from the CI’s own variables on GitHub Actions, GitLab, Bitbucket, CircleCI, Jenkins’ multibranch pipelines, Azure Pipelines, Buildkite, Cloudflare’s Workers Builds or Pages, Netlify and Vercel, and elsewhere from ROUTINGCAT_BRANCH: in any other CI or a deploy script, set it to the branch being deployed. Inside a CI RoutingCat reads, the variable doesn’t apply: a Jenkins job that isn’t a multibranch pipeline, or Azure Pipelines over TFVC, names no branch, so run the push from a multibranch pipeline, or from a Git repository. A run of a pull request, a tag or another branch takes the preview key, from npx routingcat keys deploy --env preview. A GitHub token is refused from a repository not linked to the site, or whose link expired: run npx routingcat link github, then the workflow again. |
| npx can’t run the version the config pins, with exit code 2 | Check the network, and the registry npm config get registry prints, then run the same command again. |
| The site registers from its build | The site is bound to a host whose build registers it: deploy there. A site created for that host, then moved to decide in the page, stays bound to it, and registers with the deploy key alone, in your CI or your host’s deploy script. |
| RoutingCat can’t be reached, with exit code 4 | Run the push again once it is back. |
The GitHub link waits for a run, or expired. npx routingcat link github and doctor say where it stands. A pending link waits for push to run in a workflow of that repository whose job has permissions: id-token: write, before the time it names. Once it has expired, run npx routingcat link github again, then such a workflow, or npx routingcat unlink github to keep the deploy key alone.
push --check fails in CI when a template lacks a stamp or the partial is out of date: run npx routingcat push in your repository, commit what it changes, and run the workflow again.
push and plan refuse slot markup they can’t read or that breaks its rules, each with the file, the line and the fix: markup they can’t read, the slot’s grammar, a slot the browser would move, what a hidden variant holds, and templates and config that disagree. Build a page gives each rule. Here, a hidden variant holds a script:
$ routingcat push✗ layouts/index.html:9: home/hero:studio holds a script that runs, or structured data that search engines read, even while the variant is hidden. Move it out of the variant. (D69, exit 1)Once a production push registers its changes, it registers your deployment last. If no deployment has registered since, run the push again on the same templates, which registers it or says why it can’t.
Pages show no decisions
A decision is a visitor’s browser picking their variants on a routed page. doctor checks each page’s first one.
- The page doesn’t carry the head partial, or carries it out of place. Include the partial first in each layout’s
<head>, after the charset and viewport tags. - The page isn’t live yet. Only a production push makes a page live, and until its routing data is published, within about 2 minutes, the page shows its defaults.
- The page’s head partial names templates no push registered yet (a partial no push registered). From a deploy that changed your templates until about 2 minutes after its push, your pages show your defaults and record no decision: register each deploy right after it, as Change, preview and deploy explains.
- Routing is off. The push’s receipt says so;
npx routingcat set routing=onturns 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.
- The page’s routing data is over its 128 KB. Every visitor gets its defaults until a push brings it under: retire variants, or turn personalization off for a slot.
- The page’s Content Security Policy blocks RoutingCat. Give the head partial’s script tags the page’s nonce in your template, which
pushkeeps, or add the core’s hash thatdoctornames, which an upgrade ofroutingcatcan change; then add each other source it names. Run it in production gives the whole policy. - Records can’t reach RoutingCat: it doesn’t accept them from your domain, so add the domain with
npx routingcat domains add; or the pages have sent nothing for hours, whichdoctortraces to the page. - Cloudflare runs RoutingCat’s script late, through Rocket Loader, or puts Zaraz first: see Run it in production.
- Decisions come, but no render beacon. RoutingCat’s post part doesn’t run on the page: it doesn’t load, or a policy blocks it. In your own browser, an ad blocker also stops 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:
- a push that routes a page prints
routingcat NOT LIVE routing waits for a card on file: add one at …, nothing charged; doctorshowscard on filewaiting, with the link;npx routingcat set routing=onis refused, with the way out.
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 push. 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:learnsin development, or use a preview link in production: Change, preview and deploy. - It’s paused or retired.
npx routingcat show home/hero:learnssays so, andnpx routingcat resume home/hero:learnsbrings it back. - The plan holds it. On Hobby, past the account’s 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.
showgives its share. - It was added during a visit. A slot added mid-visit shows its default to that visitor until their next visit.
- Your deployed pages don’t carry it. A variant pushed but not deployed waits for the deploy, since each page shows only the variants its own head partial lists.
- Your served HTML differs from your templates, as when a minifier drops
data-rc-*attributes orhidden. Build and deploy from the templatespushstamped. - A first view waited past its deadline. That visitor keeps the defaults on that page for the rest of the visit: see Expect a first view to wait.
- 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, an account serves at most 10 live variants, across all of its owner’s sites, the slots’ defaults not counted. A push or an act past them still succeeds, and each variant past the 10th is held: registered, and not served. The push names it after its receipt, 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 push. See Go past Hobby’s 10 live variants.
Conversions don’t join
Track conversions explains the tracking line of status. The messages behind it:
- Conversions with no key: from your server, pass
visitor_idfrom the request’src_vidcookie, oraccount_id. - Accounts never linked: identify at sign-up and at every sign-in.
- Identify without a visitor id: often a callback posted from another site, which carries no cookie. Identify at the next page load instead.
- The preview server key used in production: use the production key.
- Events under an undeclared name: declare the name in
routingcat.config.jsonif it matters. - No event yet, a silence or a drop: check that the call is still made, and after a drop, record a tracking gap.
- Conversions with no routed visit need nothing: they count once their visitors see a routed page.
- Browser events refused: RoutingCat accepts the page’s events only from your site’s domains, and only with the site’s public key, which
routingcat.config.jsonmust name.
Results leave decisions out
- Decisions that fail replay are left out of every result. For
unregistered_deployment, your pages ran a core that decided under templates no push had registered: upgrade the version your config pins, whose core shows such a page’s defaults and records nothing until its push, and runpush --checkbefore every deploy andpushright after it.doctornames a page whose partial no push registered. The other reasons are RoutingCat’s to fix. - Decisions whose record never arrived are left out too: make sure nothing on your pages or in front of them, such as a Content Security Policy or a proxy, blocks the page’s requests to RoutingCat.
A preview link fails
| What happens | Do this |
|---|---|
preview says the production deployment doesn’t carry the version |
Push and deploy it to production first. |
preview says no deployment that includes it has registered yet |
Run the production push again on the same templates, which registers the deployment last, then preview. |
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. |
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 loginin 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 push from the machine waits for the person, exit code 3: show them the plan, and run it with
--confirmonce they agree. doctor --waitwaits for a visit a person makes, up to 5 minutes: give the person the page it names to open, and give the call a 6-minute timeout, or run it in the background.
Read doctor check by check
npx routingcat doctor reads your project, your sign-in, your site’s records, and your production pages as served at each page’s url, and changes nothing. Your pages decide only in a browser, so doctor’s requests record no visit: until a first arrival comes, it names a page to open in a browser, and --wait waits up to 5 minutes for that visit. Set up RoutingCat shows a run where every check passes. Here, the page’s policy runs no inline script but by nonce or hash:
$ routingcat doctorlumen · production ✓ build registered ch_01m3yfrpt0ewpateq9yrq61xkb · objective signup · outcomes waitlist 14:19:52 ✓ head partial 1 page: first in <head>, core 1.0.0, manifest 47a096ddb5c9… registered ✓ post part rt/1/post/1.0.4.js loads, its integrity checked ✗ policy https://www.lumen.com/'s Content Security Policy runs no inline script but by nonce or hash, and RoutingCat's core carries neither, so the page never decides: visitors get your defaults, and their visits are not recorded. Give the script tags of resources/views/routingcat-head.blade.php the page's nonce in your template, which routingcat push keeps, or add 'sha256-ufFiB8WxqAPeqL6gM1yjrNQwjPr6NxbBkJftA/62vQI=' to script-src, the core's hash, which an upgrade of routingcat can change. (D92) ✓ records ingest.routingcat.com accepts them from https://www.lumen.com ✓ markup no slot on these pages yet ✓ first arrival arrivals in the pulse, last 14:39:18 14:39:18 ✓ signup 2 events 14:33:59 · server · identify 14:32:05 14:33:59 ✓ card on file Visa •••• 4242 · AGENTS.md has no RoutingCat note, so coding agents may skip the skill unless a request matches it; if the person wants the note, run npx routingcat init --agents-md8 of 9 passed · 1 failed (exit 1)| Check | Passes when | Otherwise |
|---|---|---|
| Your files, shown only when one fails | The config valid, with the site’s public key; your templates’ stamps and partial as push writes them; the partial’s include; the server helpers your config declares, and their registration; on Laravel, rc_vid left out of the cookie encryption; an up-to-date skill |
The wrong public key, stamps or a partial out of date, an edit to make, the skill |
| build registered | A production push registered, with the objective and outcomes your config declares | Waits for a production push |
| GitHub link, on a linked site | A workflow run of the repository proved the link | It waits, or it expired |
| head partial | Each page carries it, readable, first in <head>, and naming templates a push registered |
Before the site’s first deploy, waits for that deploy and its push. Then the partial, and one no push registered yet, which waits for that push, then fails at the end of --wait; Zaraz placed before it |
| bundle | RoutingCat’s CDN has routing data for each routed page, for a core it hasn’t turned off, listing the templates your pages name | Waits for a production push that makes the page live, and after a push, about 2 minutes for routing data that lists its templates; routing data that doesn’t list them, a core RoutingCat turned off |
| post part | It loads as pinned, its integrity checked | The post part doesn’t load |
| policy | The page’s Content Security Policy lets RoutingCat run, apply and record | The source to add |
| records | RoutingCat’s edge accepts records from your production address | The domain to add |
| markup | Each page’s slots, as served, match your templates, with registered hashes | Served markup that differs |
| server helpers, where your config declares them | Each page sets the cookies they set | Helpers that don’t run |
| Cloudflare’s features, behind Cloudflare | Rocket Loader leaves RoutingCat’s core to run first | Rocket Loader running it late |
| first arrival | A browser’s visit arrived, in the pulse | Waits for a person to open the page it names |
| 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 |
| server key, browser bundle, shown only when one fails | The preview server key used in production, a page over its 128 KB of routing data |
Lines marked · are information and need nothing: a newer core or post part than your pages run, 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.