Troubleshooting & known issues
Symptom-first catalog of what users and operators actually hit — slow loads, demo ribbons, unit caveats, stale coverage — and what each one means.
Everything on this page was reproduced against a live gcs-mode stack during QA. Symptoms first, then what is actually happening, then what (if anything) to do. The runbook covers the re-arm procedures; this page covers recognition.
"I picked a depth and nothing happened for a minute"
Picking a stack depth whose pack the API has not loaded yet makes the API download, sha256-verify and JSON-parse the whole depth pack before the first node can be served. Measured cold: ~50 s for the 10bb pack, and 3+ minutes for the 100bb pack (~122 MB) on a contended link. The UI shows a spinner on the depth row ("loading pack…") and keeps the current screen interactive; a cold hard visit renders a skeleton instead. Every later request for that depth is served from API memory — warm depths switch in under a second.
Operator move: pre-warm after any API restart
(curl -s $API/v1/preflop/100 > /dev/null, repeat for the depths you demo).
"Everything suddenly says Demo data"
The amber ribbon means this page's data came from the built-in mock, and in a configured stack it appears when a live fetch fails. The usual causes, in order of likelihood:
- GCS token expired (gcs mode, local dev) — see the runbook. Note that pages you visited recently may keep working (Next caches immutable node/board documents for an hour), so the ribbon shows up first on screens you had not opened yet. That mixed state — some pages live, new ones demo — is the token-expiry signature.
- API process down or unreachable — same mixed behavior. Check
GET /v1/health. localhostinstead of127.0.0.1inOLLIN_STUDY_API_URL— the API binds IPv4-only; Node resolveslocalhostto::1first and every fetch fails instantly. The stack looks permanently mocked from the first request.
One sharp edge while the API is down: a live family URL (e.g.
/study/100bb_srp_btn_bb_p7v5_n22) whose data is not in the app's cache
returns a 404, because the mock catalog only knows its own fabricated
families. The page is back the moment the API is.
"The pot on the family page doesn't match the pot on the bridge"
Fixed in API adapter rev 2 — if you still see it, you are looking at a stale cache (hard-refresh; the rev-2 ETags force revalidation).
The history, for the record: the preflop tier (HRC source of truth) uses a
flat big blind of 100 chips (settings.bb_chips = 100). The postflop v0
packs carry chips with no explicit bb constant, and the rev-1 serving
adapter derived one as bb_chips = eff_stack_chips / depth_bb — which
by construction pinned every family's effective stack readout to its depth
and inflated every other bb number by the preflop investment share:
| Spot (100bb) | bridge (true bb) | rev-1 family header | inflation |
|---|---|---|---|
| single-raised | pot 7.46 / eff 96.88 | pot 7.70 / eff 100 | ×1.03 |
| 3-bet pot | pot 41.8 / eff 80.86 | pot 51.69 / eff 100 | ×1.24 |
| 4-bet pot | pot 80.3 / eff 60.46 | pot 132.8 / eff 100 | ×1.65 |
Ratios (SPR, pot odds, MDF, frequencies, EV ordering) were never affected —
only absolute bb labels. Rev 2 converts postflop chips with the same flat
100 chips/bb the preflop tier declares, so both surfaces now agree exactly;
documents carry meta.bb_chips: 100 and the rev-1 bb_chips_derived flag
is retired. Nothing solver-side changed: packs always stored raw chips, and
the solver/training stack works in pot-normalized units throughout.
"A boundary says solved/locked but the family exists"
Coverage labels (served/solved/library/none) are baked into the
preflop packs at extraction time; the bridge trusts them without
re-checking. As of stamp 20260721T131354Z every boundary with a postflop
artifact is labeled served (242 total across the ladder) — if you still
see a locked boundary that should be live, you are looking at a cache,
not the data:
- the API caches a pack for up to
MANIFEST_TTL_MS(5 min) after the tier manifest changes; - the app caches node responses (which embed the boundary labels) for up to
1 hour in Next's fetch cache — and in dev that cache lives on disk in
.next/cacheand survives a dev-server restart. Delete it (or wait) after republishing a tier.
"The family page takes seconds before anything appears"
There is no aggregate endpoint yet, so the family screen builds its flop
report by fetching all 184 board documents (bounded concurrency). Warm, that
is a few seconds server-side; cold it also pays one family-pack fetch. A
skeleton renders meanwhile. The fix on the table is
/v1/families/{fam}/aggregate (open question with the API team) or
streaming the report table separately.
"gcloud gave me a token but it died in minutes"
gcloud auth print-access-token returns the cached credential, which may
have almost no lifetime left. If a long operation (cold catalog build,
pack warm-up) is about to start, check the remaining lifetime first:
TOKEN=$(gcloud auth print-access-token)
curl -s "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=$TOKEN" \
| jq .expires_inAnything under ~20 minutes: wait for expiry and mint again (gcloud refreshes once the cached one lapses), or fix ADC and drop the escape hatch. The catalog build aborts cleanly on mid-build auth failure and keeps serving its previous state; a cold start with a dead token 502s until re-armed.
Known issues (accepted for now)
- Family lineage shows no sizes ("Open / Call" instead of "Open 3.0"). Deliberate: the true sizes are not recoverable from the family wire, and the previous pot-derived guess misstated them (a 3.0bb open rendered as "Open 3.9"). The exact line lives one screen up in the preflop tree; serving lineage on the family document is a data-plane request.
- IP range weights are uniform at tier
root— badged in the UI ("IP weights: uniform (v0)"); real weights arrive with the tree tier. - Preflop EVs absent — the Breakdown tab says so instead of fabricating; HRC EV exports are a future extraction.
- First
/docscompile in dev takes ~2 minutes (mermaid). Dev-only; production builds pre-render. /v1/familiescold start in gcs mode takes ~15 minutes (242 pack fetches at concurrency 3 — the v0 layout has no trustworthy manifest, so the catalog adapts every pack to index it). Subsequent refreshes only fetch changed packs. Keep the process warm; do not schedule restarts right before demos.