# Perf Debug

> End-to-end front→back performance debugging for the Kodus web app. Use when a screen is slow, blank, looping, or you need to trace a UI perf problem down to the API query. Drives Playwright/Chrome MCP to open + measure the screen, then reads the API use-case/repository and the DB (Postgres/Mongo indexes, explain) to find and fix the root cause, verifying live.

- Skill: `kodustech/perf-debug` (Agent Skill)
- Install (CLI): `npx skillmds@latest add kodustech/perf-debug`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kodustech/perf-debug/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: kodustech (https://skillmd.com/u/kodustech)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/kodustech/perf-debug

---


# Perf debug (front → back)

Repeatable loop for resolving Kodus web performance problems, from the rendered
screen down to the DB query. Built from real sessions on the token-usage,
pull-requests, settings and cockpit screens.

## The loop

1. **Open the screen** — Playwright (`mcp__plugin_playwright_playwright__*`) or
   Chrome MCP. App is auth-gated; `/` is 404 post-Next16, log in at `/sign-in`
   (email → Continue → password). Test user: `Novus@teste.com` (org
   `aae1c003-…` has real data). Reset its password via Postgres if needed
   (bcryptjs hash; see the memory).

2. **Observe — never trust the first load.** Capture `browser_console_messages`
   (errors) + `browser_network_requests` (fan-out). **Reload 2× on a settled
   dev server**: HMR produces phantom hydration errors / truncated-parse 500s.
   Only what reproduces on a clean load is real.

3. **Measure** via the Performance API (`browser_evaluate`):
   `nav.responseStart` (TTFB), FCP, LCP, load. Classify the bottleneck:
   - high TTFB → **SSR-data-bound** (server component awaiting slow data)
   - big JS / slow FCP after TTFB → **bundle-bound**
   - janky interaction → **render-bound** (note: React Compiler is ON, so
     manual memo is rarely the fix).

4. **Understand the components** — read the page.tsx / client component. Find the
   server/client split and the loading states. Watch for `if (!isMounted) return
   null` (blanks the body pre-hydration → show a skeleton instead) and effects
   that `router.replace` with `searchParams` in deps (reload loops).

5. **Trace to the API** — from the fan-out, pick the heavy/redundant endpoints
   (duplicates, per-page `/api/auth/session`, `/executions`, cockpit fan-out).

6. **Understand the query** — read `apps/api/src/controllers/*` →
   `libs/**/use-cases` → repository. Then hit the DB directly:
   - Postgres: `docker exec kodus_api printenv API_PG_DB_PASSWORD`, then
     `docker exec -e PGPASSWORD=… db_postgres psql -U kodusdev -d kodus_db`.
     Check `pg_indexes` for the table; look for OFFSET-in-loop, uncached
     `COUNT(*)`, N+1 (`relations:[...]` on a to-one is a JOIN, not N+1).
   - Mongo: `docker exec mongodb mongosh "mongodb://kodusdev:<pass>@localhost:27017/<db>?authSource=admin"`.
     `db.<coll>.getIndexes()` (watch for **partial indexes** matching the
     filter), `.explain("executionStats")` — compare `totalKeysExamined` vs
     `nReturned`. See the `mongodb-query-optimizer` skill for deeper analysis.
   - Kodus DB is generally **well-indexed**; the real backend wins are usually
     algorithmic (keyset vs OFFSET) or caching — and only reproduce at prod
     scale (dev DB is tiny), so prepare the patch and validate in staging.

7. **Fix surgically + verify live** — one change, then re-measure + re-check
   console on a clean reload. Confirm no new hydration errors, no 500.

8. **Isolate when unsure — revert to compare.** If a change might be the cause,
   `git checkout -- <file>` (back it up first) and reload: if the symptom
   persists with the original, your change is exonerated. (Used to prove the
   token-usage blank was a pre-existing reload loop, not the recharts port.)

## Dev-env gotchas (these cost hours if unknown)

- **HMR noise** → hydration/parse errors that vanish on a 2nd clean reload.
- **Turbopack truncated-parse cache** (`Unexpected eof` on a valid file) →
  `touch` the file to force a fresh re-read. Root cause: `CHOKIDAR_USEPOLLING=false`
  + `:delegated` bind-mount catches partial writes.
- **OOM** → heavy routes (charts + turbopack) blow the web container's memory
  limit. Check `docker stats` / `docker inspect --format '{{.State.OOMKilled}}'`;
  bump `deploy.resources.limits.memory` (web needed 4G, 2G OOM'd).
- **Reload loop** → a page hammering its own document (`GET /x` ×1000s, CPU
  pegged, blank body). Diagnose via request count in `docker logs`. Cause is
  usually an effect that navigates with the value it depends on in its deps.
- **Cache masks slow loads** — the token-usage `$facet` is ~7s cold / ~400ms
  cached; bust the cache (uncached filter combo) to observe the real load.
- **Healthcheck false-unhealthy** — `kodus_web` pings `/` (404 post-Next16) so
  it shows "unhealthy" while working.

## Reference
See memory `reference_perf_debug_flow` for the condensed version and
`project_frontend_perf_next16` for the concrete fixes shipped with this flow.

