Troubleshooting
Symptoms first, then the cause and the fix.
Admin: "ADMIN_SECRET_KEY is not set"#
Fix: cd admin && npm run init. It writes admin/.env once and never overwrites an existing key. If you restored a backup, put your old .env back instead; a new key cannot decrypt secrets stored under the old one.
Admin: port 8811 already in use#
Fix: start on another port: PORT=8812 npm start, or add PORT=8812 to admin/.env.
Admin: lost the one-time setup code#
Fix: restart the admin. While no owner exists, every start prints a new code.
Scanner: "Could not load board.json"#
- You opened the HTML file directly. Browsers block
fetch()fromfile://. Serve the folder:python3 -m http.server 8777. - The file is missing. Run the refresh job and pull, or check that
scanner/public/board.jsonwas deployed.
A venue is "unavailable" or a fetch fails with a generic error#
A network that blocks a domain often looks like an ordinary connection or TLS error. In some countries internet providers block venue domains such as *.polymarket.com by law. To tell a block from an outage, compare your resolver with a public one:
dig +short gamma-api.polymarket.com # your provider's resolver dig +short @1.1.1.1 gamma-api.polymarket.com # a public resolver
If the two answers differ, your network is redirecting the domain and the venue itself is fine. Do not try to get around a legal block. The refresh job runs on GitHub's runners, which are not behind your provider; that is how the data is meant to be fetched. The workflow log's "Probe venue reachability" step shows what the runner sees.
Kalshi answers 403#
Cause: the request carried an Origin header. Kalshi refuses every such request, which means any request made by a browser page. Fix: fetch Kalshi only from the job (Node sends no Origin). Do not add a browser call to Kalshi.
Polymarket host does not resolve#
clob-v2.polymarket.com has no public DNS record (checked from two public resolvers and a GitHub runner). The job uses https://clob.polymarket.com for order-book reads; if you set POLYMARKET_CLOB_HOST, use that host.
The pages show old data#
- The schedule is off by default; the data is from the last manual run. See Turn on the schedule.
- Your host did not redeploy after the job's commit. Redeploy or connect the host to git.
- A CDN caches JSON. Set
Cache-Control: no-storefor*.json(Vercel: already invercel.json). - A schedule on a quiet repository may have been paused by GitHub; re-enable it in the Actions tab.
A setting does not appear on the live site#
- Is
config/public.jsoncommitted and deployed? The admin writes the file; it does not deploy. - Is it reachable at
/config/public.jsonon your site? - Admin Status: is
publicConfiggreen? - Is the value valid? The pages ignore an accent that is not
#rgb/#rrggbb, a logo that is not an https ordata:imageURL, a support email that is not an address, and a channel link that is nothttps://t.me/….
The refresh workflow fails#
| Failing step | Meaning |
|---|---|
| Build board | The Scanner job crashed. The log shows the error; a venue outage alone does not crash it (the venue is reported unavailable). |
| Fail if the run fabricated anything | A data integrity rule was broken. Do not bypass it; the data would contradict itself. |
| Read the Terminal tape from Polygon | All three public RPCs failed or returned an error. Usually temporary; run again. |
| Commit the new board | Push refused: check workflow permissions (Settings → Actions → General), or a concurrent push (run again). |
"0 positive spreads" or "0 candidates"#
This is the real result, not a fault. In the last committed run, no single-venue spread was positive after fees and every cross-venue pair was rejected by at least one veto. scanner/public/crossvenue-diag.json lists the veto counts and the 150 highest-scoring rejected pairs with their reasons, so you can see whether a rule is too strict. See the matcher.
A category I hid still shows#
- Open the live
/config/public.jsonin a browser and look atcategories.hidden. If it does not list the category, the new file is not deployed yet: commit and deployconfig/public.json(see A setting does not appear). - If it is listed, the market is labelled differently than you expect. Open the browser console on the page and run
MikodesCategories.classify({ question: "…the market title…", slug: "…" })to see its label. Polymarket rows often have no category, so the title and slug decide; add a pattern indesign-system/categories.jsand runsh design-system/build.sh. - To clean the committed data files at once:
node scanner/src/category-filter.jsandnode terminal/src/category-filter.jsfrom the repository root (on Windows, in WSL or Git Bash), then commit and deploy.
The lists are short, or a "Hidden" notice appears#
That is the category filter at work. Much of Polymarket's on-chain activity in any one minute is sports, esports and short-term up/down markets, which the default setting hides, so the Terminal may list only a few tokens and fills. The Hidden notice says how many rows were left out. The KPI tiles still count every market in the window. To show more, remove categories in admin → Categories; to read a larger window, raise the block count in the workflow step node src/onchain.js 40 (more RPC calls per run).