-
Notifications
You must be signed in to change notification settings - Fork 0
Troubleshooting
Port 5173 is already in use
This is deliberate. vite.config.ts sets strictPort: true, so Vite fails loudly instead of drifting to 5174 — because start_app.bat opens 5173 regardless, and a silent drift lands the browser on a dead URL with the app looking broken and nothing printed anywhere.
Find what holds it:
netstat -ano | findstr ":5173" # Windows — last column is the PID
lsof -i :5173 # macOS / LinuxUsually it is a dev server you already have running. Use that one.
The frontend proxies /api to 127.0.0.1:8000. If the backend is not up, every call fails while the page itself renders fine.
Check the backend directly:
curl http://127.0.0.1:8000/docsIf that fails, start it and read its output — a failure during apply_lightweight_migrations() or seeding will surface there, not in the browser.
Symptoms are SSL/TLS verification failures against PyPI that look like a network outage but persist on a working connection.
The usual cause is antivirus HTTPS interception — Norton, Kaspersky, ESET and others terminate TLS and re-sign with their own root, which Python's bundled certificate store does not trust. It breaks pip, uv, and requests alike.
Options, in order of preference:
- Use a tool that can read the OS trust store —
uvsupports--system-certs. - Point Python at the interception root explicitly via
REQUESTS_CA_BUNDLE/SSL_CERT_FILE. - Exempt PyPI in the antivirus settings.
Disabling verification is not on this list.
python on PATH is frequently not the interpreter you think, particularly on Windows where the Store shim, an old system install, and a py launcher entry can all disagree. A stale py -3.13 registry entry pointing at a removed install is a common one.
Pin it explicitly rather than trusting python:
python -V # confirm before creating the venv
py -0 # Windows: list what the launcher actually knows aboutPrepBench targets 3.14. A pydantic-core build from source is the usual sign you are on an interpreter with no matching wheel.
If tsc reports missing declarations for a package whose JavaScript clearly works, the install may have extracted partially — .js present, .d.ts absent. Interrupted installs and antivirus quarantine both cause it.
Reinstall the locked version:
npm ciDo not "fix" it by bumping the package. A version bump that appears to resolve a missing-types error has usually just replaced a broken extraction with a working one, and you have taken an unrelated upgrade along with it.
Working as designed when no provider is configured, and the app will not invent a score to fill the gap.
- Settings → AI Providers — is a provider configured at all?
- Is it bound to the task? Routing is per task; grading and recording analysis bind separately.
- Hit
POST /api/v1/llm/providers/{id}/verifyand read the failure.
See AI Providers.
Almost always because the sessions being counted are drills, and readiness counts full mocks only.
Note
No screen starts a mock yet. POST /api/v1/exams accepts session_kind and subject_id, but nothing in the UI sends them, so every session the running app creates is a drill. This is pinned by a test rather than left to drift. Until an exam-setup path sets it, browser-driven practice cannot move readiness.
To confirm what is actually stored:
SELECT session_kind, status, COUNT(*) FROM exam_sessions GROUP BY 1, 2;If they are all drill, that is the reason. The other causes, in the order worth checking:
| Cause | Check |
|---|---|
| The session is not finished | Only COMPLETED sessions with a non-null score count |
| It resolved to no subject | A session matches by subject_id, falling back to the certification string. A skill subject has no certification, so a session with no subject_id can never belong to one |
| It is a skill subject | No pass mark means developing is the ceiling, by design — see Readiness
|
| Fewer than three mocks |
ready needs three, and the last three consecutive at or above the pass mark |
| The last mock is older than 14 days | Stale evidence is not evidence; the state drops back |
A domain showing no percentage rather than a low one is also working as intended: below ten answered questions there is no reportable score, and 0% would read as a failure that has not happened.
It should not, and if it did, the seed ledger is the place to look:
SELECT namespace, content_key FROM seeded_content ORDER BY 1, 2;Every built-in this install has ever been offered is listed there, whether or not it is still present. A missing row is why an item was recreated.
The usual cause is that the item's key changed. Keys are the readable identity — a prompt or review title, a subject name, round:question_text for an interview question — so editing one in the seed file makes the next boot see a new item and create it alongside the old one.
Check the ledger the same way. One case creates rows without creating content, deliberately: a database created before the ledger existed holds content but no record of it, and nothing can distinguish "the user deleted this" from "this was never shipped". On that one boot everything currently in the built-in list is marked as offered and nothing is created. From the next boot on, only genuinely new items arrive.
If you want the full built-in set back, Settings → Reset empties every table — the ledger included — and reseeds from scratch. It also deletes your study data.
| Cause | Check |
|---|---|
| Secret does not resolve | The stored reference is a pointer. If it is env:NAME, that variable must exist in the running backend's environment — restart after editing .env
|
| Keyring not installed | A keyring: reference with the package absent logs a warning and resolves to nothing. Install keyring or re-store the secret |
| Local model not running | PrepBench never launches one for you. Start it yourself, then verify |
| Wrong base URL | For OpenAI-compatible providers the path suffix matters — most want the root, not /v1/chat/completions
|
There is no Alembic. Startup adds missing columns and indexes only. Renaming a column, changing its type, or dropping one will not happen, and will not error either — the old column simply stays and the new one appears empty.
See Architecture.
Check whether you are hitting the 20-second testTimeout in vite.config.ts rather than a real hang. The heaviest tests drive a full MUI dialog through render → type → save → refetch, around 7 seconds even in isolation, and a loaded machine can push that over.
Do not add retries. A retried test that passes has told you nothing.
If a chart canvas measures width: 0 while its container has a real width, Chart.js never received a size — it reads the container through a ResizeObserver, which only fires when the page is compositing frames.
This happens in headless or hidden-pane contexts, not in a normal browser. Confirm with:
requestAnimationFrame(() => console.log('compositing'))If that never fires, the page is not painting and the canvas size is a symptom, not the bug.
The database is one file:
backend/data/exam_simulator.db
Delete it and restart for a clean slate. Built-in system design prompts, interview questions, design reviews and subjects reseed; imported content does not. There is no other copy. Nothing was uploaded anywhere; that is the whole point of the project, and it applies to your mistakes as well as your privacy.
Settings → Reset is the same outcome without deleting the file: every table emptied, including the seed ledger, then the identical seeding that startup runs.