Skip to content

Troubleshooting FAQ

Fighter90 edited this page Jul 9, 2026 · 1 revision

Troubleshooting & FAQ

Common issues

"Parent project not found" / red Health page

The server couldn't resolve the career-ops root. Resolution order: CAREER_OPS_ROOT env → ..cwd(); the first candidate containing cv.md or portals.yml wins. Fixes:

  • Make sure the UI lives at career-ops/web-ui/ (the nested layout), or
  • Set CAREER_OPS_ROOT=/path/to/career-ops before npm start.
  • Check #/health — it names exactly which checks fail.

Parent-relay endpoints (/api/followup, /api/stats/patterns, /api/stats/lifetime, /api/stats/salary-gap) are fail-soft: without the parent scripts they return {available:false, reason:'script-not-found'} with HTTP 200 and the UI shows an honest "not available" note — that's by design, not an error. An empty tracker likewise surfaces as {available:true, empty:true} ("nothing yet"), not as a script error (fixed in v1.117.2).

No LLM key set

Nothing breaks: every ⚡ live endpoint (/api/evaluate, /api/deep, /api/mode/:slug, drafts, generators) falls back to {mode:'manual', prompt} — a ready-to-paste prompt for your AI CLI. Set any one provider key in #/config (or npx career-ops-ui init) to go live. GET /api/status/providers shows what the router would pick.

PDF generation fails / Playwright missing

GET /api/stream/pdf spawns the parent's generate-pdf.mjs, which needs Playwright in the parent's node_modules (plus its headless browser shell: npx playwright install). The Health page has a setup hint for this. career-ops-ui doctor verifies it.

Port 4317 busy

Another instance (or another app) holds 127.0.0.1:4317. Stop it or set PORT=<other> when starting. Tests never assume 4317 — they bind port 0.

Scanner returns nothing

  • Check portals.yml: ATS sources walk tracked_companies: — no companies, no results. RU sources read the russian_portals: block (sources, queries, area, per_page, only_remote).
  • Title filter: the parent's title_filter.positive keywords must match your target roles — overly narrow filters silently drop everything.
  • Quarantine & dedup: results already seen are deduped against data/scan-history.tsv; suspicious postings are quarantine-filtered. Run with dryRun=1 and watch the SSE log lines to see what got filtered and why.
  • hh.ru from outside Russia: hh.ru is scraped from its HTML search (the JSON API 403s programmatic clients) and may geo-block; see help §7.
  • Workday: some tenants fall back — check workdayFallback in GET /api/scan-results.

The SPA looks stale after an update

Hard-reload — scripts are served as-is from public/ with no build hashes.

Hard-won lessons (from the repo's CLAUDE.md)

The traps that each cost a release:

  1. [hidden] is a no-op against an author display: rule. Author CSS beats the UA [hidden]{display:none}. If a component class sets display: flex/grid, add an explicit .selector[hidden]{display:none} override — or toggle a class instead.
  2. npm test 2>&1 | grep … masks the exit code. grep returns 0 on match even when the suite failed — two releases shipped failing tests this way. Run the suite first, capture $?, grep separately. Same for git commit … | tail hiding hook failures.
  3. cleanLlmMarkdown is NOT an XSS sanitizer. The XSS boundary is UI.md() (client) and stripDangerousMarkdown() (CV ingress). Mixing the declutter step into either is a category error.
  4. Pre-commit AI review is advisory; ci.yml is the hard gate. Green pre-commit + red CI is possible. Watch the CI run.
  5. PATHS resolves once per process. Tests bootstrapping their own CAREER_OPS_ROOT must dynamic-import() every paths.mjs carrier inside before() — a top-level static import pins the real parent and leaks writes into the user's live files.
  6. UI.toast() parses its own (METHOD /path · HTTP NNN) postfix — don't pre-strip it at the call site; the notifications drawer is the only place that re-surfaces toasts (use UI.toast, not ad-hoc console.log).
  7. Grep treats emoji-heavy JS as binary — an empty grep over locale dicts may be a false negative; use grep -a or rg.
  8. CodeQL rate-limiting/FS-write findings on new shell-out routes are known categorical false positives — dismiss with the documented rationale (the custom middleware isn't credited), and still add real hardening where it belongs.

FAQ

Is this the official career-ops UI? No — it's an unofficial companion, not affiliated with or endorsed by career-ops/santifer. It tracks the parent with parity releases.

Does it send my data anywhere? Only where you point it: provider API calls when you enable ⚡ live features, and public job-board APIs during scans. No telemetry, no cloud accounts, no auto-submit. Logos and model catalogues are proxied server-side so the browser only ever talks to 'self'.

Can it apply to jobs for me? No. It drafts, evaluates, fills, and tracks — the final Submit is always yours (parent's ethical-use rule).

Can I expose it on my LAN? It's designed for loopback. If you bind HOST=0.0.0.0, rate limiting activates and health output is redacted, but there is no authentication layer — treat public exposure as at-your-own-risk. See Security.

Where do I report bugs? The in-app bug reporter (notifications drawer) builds a privacy-floored, pre-filled GitHub issue — or file one directly at https://github.com/Fighter90/career-ops-ui/issues.

Clone this wiki locally