Skip to content

Live-run synthesis fix + Console backend robustness - #8

Merged
anmolg1997 merged 2 commits into
mainfrom
fix/live-run-and-console-robustness
Jun 5, 2026
Merged

Live-run synthesis fix + Console backend robustness#8
anmolg1997 merged 2 commits into
mainfrom
fix/live-run-and-console-robustness

Conversation

@anmolg1997

Copy link
Copy Markdown
Collaborator

Why

Follow-up to #7 (merged). Two commits landed on the branch after that PR merged, so they're not on main yet. They came out of a genuinely-live p2p end-to-end test (rename out/ aside so nothing replays, run fresh):

  1. A real live-only bug: synthesis aborted the whole suite with TypeError: 'str' object is not a mapping.
  2. The actual cause of the UI "⚠ Failed to fetch".

Bug 1 — live synthesis crash (the important one)

A fresh p2p run did its live discovery fine (4 findings), then synthesis crashed:

! synthesis failed for 'p2p' ... (TypeError: 'str' object is not a mapping).

Root cause: the live agent sometimes emits strategy_profile as a bare string instead of an object; build.py did {**strategy_profile}, and spreading a string raises that exact error. Golden/cached runs never hit it because their emits are well-formed — so it only bites real live runs (the path under test).

Fix (build.py): coerce strategy_profile to a dict at both the spread site and in _from_payload — a stray string is discarded (the typed StrategyProfile still wins) instead of crashing the suite. +2 regression tests (direct mapper + full fan-out).

Bug 2 — the UI "⚠ Failed to fetch"

Root cause: a stale Console backend still holding port 8742 made a new server.py crash silently (OSError: Address already in use); Vite then started anyway, so the UI loaded with no backend to call.

Fix:

  • server.py — catch EADDRINUSE and exit 1 with a clear "port in use — stop the other backend or set DISCOVERY_UI_PORT" message (no traceback); allow_reuse_address so a just-stopped backend's TIME_WAIT socket doesn't block restart.
  • tasks.py (console) — poll /healthz until the backend answers before starting Vite; if it never comes up, abort with the stop-the-stale-one hint instead of opening a UI that can only say "Failed to fetch". Also (from e3d7646): the console command is self-sufficient (renders the offline golden suite first if out/ is empty), opens the real Vite URL (handles a bumped port), and tears down both servers' process groups cleanly on Ctrl-C.

Verified

  • Fresh run.py --fresh p2p (with out/ renamed aside) → full 6-report suite, exit 0, 4 findings, strategy_profile a dict. No cache, no fixture.
  • Console path (POST /api/run {mode:live} → SSE) streams real live agent activity end-to-end.
  • server.py second-instance on a busy port → graceful exit + clear message (no traceback).
  • Gates: 250 tests, 100% branch coverage, pyrefly clean (product code + tasks.py).

If "Failed to fetch" ever recurs, the one-liner is lsof -ti tcp:8742 | xargs kill, then re-run.

…teardown

`make console` already ran both localhosts (backend :8742 + Vite dev server) and
kept them alive — confirmed that's the "build both + keep running" command. Three
fixes found while verifying it:

- Auto-open the app once Vite is serving, reading the REAL url from Vite's own
  "Local:" banner (Vite bumps off a busy 5173, so a hardcoded port was wrong).
- Self-sufficient: if v1/out has no discovery-*.json, render the offline golden
  suite first (no key, no cost) — a first `make console` no longer dies on the
  explorer's sync-data step ("cannot read v1/out") with a confusing Node error.
- Robust teardown: start the backend + dev server each in their OWN process group
  and kill the group on Ctrl-C, so grandchildren (npm -> vite -> esbuild) are
  reaped too instead of leaking. Cross-platform (setsid/killpg on POSIX,
  CREATE_NEW_PROCESS_GROUP on Windows).

README: give the Console its own paragraph (no key needed; opens the browser;
Ctrl-C stops both). Verified end-to-end: both ports serve, opens the correct
(bumped) port, SIGINT leaves 0 stray procs; empty-out auto-generates then starts.
pyrefly clean on tasks.py; suite 248 pass.
…nd robustness

Validated by a genuinely-fresh p2p E2E run (out/ renamed aside so nothing replays
from prior output): the live agent ran, but synthesis aborted the whole suite with
"'str' object is not a mapping". Root cause: a live section emits strategy_profile
as a bare string; build.py's `{**strategy_profile}` spread chokes on a str. Golden/
cached runs never hit it because their emits are well-formed — so it only bites
real live runs (exactly the path under test).

- build.py: coerce strategy_profile to a dict at both the spread site and in
  _from_payload, so a stray string is discarded (the typed StrategyProfile still
  wins) instead of crashing. +2 regression tests (direct mapper + full fan-out).
  Suite 250 pass, 100% branch coverage held.

Also fixes the UI "⚠ Failed to fetch": its root cause is a stale Console backend
still holding :8742, which made a new server.py crash silently (OSError: address
in use) — Vite then started, so the UI loaded with no backend to call.
- server.py: catch EADDRINUSE and exit 1 with a clear "port in use — stop the other
  backend or set DISCOVERY_UI_PORT" message (no traceback); allow_reuse_address so a
  just-stopped backend's TIME_WAIT socket doesn't block restart.
- tasks.py (console): poll /healthz until the backend answers BEFORE starting Vite;
  if it never comes up, abort with the stop-the-stale-one hint instead of opening a
  UI that can only say "Failed to fetch".

Verified end-to-end: fresh p2p run.py --fresh → full 6-report suite (4 findings);
and the Console path (POST /api/run mode=live → SSE) streams real live agent
activity. pyrefly clean on product code + tasks.py.
@anmolg1997
anmolg1997 merged commit a902dcc into main Jun 5, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant