[eric] ci: a test process never spawns a router (OSW_NEVER_SPAWN_ROUTER), the boot hook's sync npm install hung the suite on every router-less runner; the slot-size test locates its source on Windows (ENG-486)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
ciregenz
2026-09-06 20:58:03 -07:00
co-authored by Claude Fable 5.1
parent 96cb953a27
commit 51bd1f990a
5 changed files with 60 additions and 4 deletions
+1
View File
@@ -28,6 +28,7 @@ jobs:
env:
OSW_NEVER_KILL_ROUTER: '1'
OSW_DISABLE_AUTO_RESUME: '1'
OSW_NEVER_SPAWN_ROUTER: '1'
# The packaged app spawns its backend with PYTHONUTF8=1 (electron/main.js); the suite runs the way the product runs, or Windows' cp1252 default fails collection on the first source read.
PYTHONUTF8: '1'
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
+19 -2
View File
@@ -414,10 +414,28 @@ def p_report_start_failure(reason: str, *, detail: str = "", **fields: Any) -> N
logger.debug("9router start-failure diagnostic submit failed", exc_info=True)
def spawn_held_because() -> str | None:
"""A declared switch, never an incidental fact: the suite and CI set it, the product never does."""
if os.environ.get("OSW_NEVER_SPAWN_ROUTER") == "1":
return "OSW_NEVER_SPAWN_ROUTER=1 (a test run adopts a router or has none)"
return None
p_spawn_hold_said = False
async def ensure_running():
"""Start 9Router if not already running. Serialized so concurrent callers
(the background auto-start + a dispatch-time ensure) can't double-spawn."""
global p_start_lock
global p_start_lock, p_spawn_hold_said
held = spawn_held_because()
if held:
# The boot-time start ran a sync `npm install 9router` (up to 300 s) INSIDE the event loop on a runner with no
# router, and the whole suite hung behind it (macOS CI, 2026-09-06). A held spawn says so once and never blocks.
if not p_spawn_hold_said:
p_spawn_hold_said = True
logger.warning(f"[9router] NOT starting the router because {held}; every lane that needs it is unserved in this process")
return
if p_start_lock is None:
p_start_lock = asyncio.Lock()
async with p_start_lock:
@@ -566,7 +584,6 @@ async def p_ensure_running_impl():
if p_process is not None and p_process.poll() is None:
logger.info("9Router already running (ours) on port %d", NINE_ROUTER_PORT)
return
import subprocess as p_sp
try:
p_stale = stale_router_pids()
if p_stale and router_kill_held_because():
+2 -1
View File
@@ -24,7 +24,6 @@ assert "backend.config.paths" not in sys.modules, "conftest must set OPENSWARM_D
os.environ["OPENSWARM_DATA_ROOT"] = tempfile.mkdtemp(prefix="osw_test_data_")
import asyncio
from datetime import datetime, timezone
from types import SimpleNamespace
import pytest
@@ -48,6 +47,8 @@ def _isolate_browser_state(monkeypatch):
monkeypatch.setenv("OSW_DISABLE_AUTO_RESUME", "1")
# A suite run must never kill a router it did not start; that is the user's app (ENG-393).
monkeypatch.setenv("OSW_NEVER_KILL_ROUTER", "1")
# ...and never START one: the boot hook ran `npm install 9router` inside the event loop on a router-less CI runner and hung the suite.
monkeypatch.setenv("OSW_NEVER_SPAWN_ROUTER", "1")
monkeypatch.setenv("OSW_PRESTAGE", "0")
monkeypatch.setenv("OSW_FASTREAD_HOP", "0")
monkeypatch.setenv("OSW_PRELUDE_TRIM", "0")
+36
View File
@@ -0,0 +1,36 @@
"""A test process never starts a router: the boot hook's sync npm install hung the whole suite on a router-less runner."""
import logging
import pytest
from backend.apps.nine_router import process
@pytest.mark.asyncio
async def test_a_held_spawn_never_reaches_the_installer_and_says_so_once(monkeypatch):
monkeypatch.setenv("OSW_NEVER_SPAWN_ROUTER", "1")
process.p_spawn_hold_said = False
async def boom():
raise AssertionError("the installer ran under a held spawn")
monkeypatch.setattr(process, "p_ensure_running_impl", boom)
seen = []
handler = logging.Handler()
handler.emit = lambda rec: seen.append(rec.getMessage())
process.logger.addHandler(handler)
try:
await process.ensure_running()
await process.ensure_running()
finally:
process.logger.removeHandler(handler)
said = [m for m in seen if "NOT starting the router" in m]
assert len(said) == 1, said
def test_the_suite_and_ci_declare_the_hold():
import os
import pathlib
assert os.environ.get("OSW_NEVER_SPAWN_ROUTER") == "1", "conftest must set it"
ci = pathlib.Path(".github/workflows/suites-matrix.yml").read_text(encoding="utf-8")
assert "OSW_NEVER_SPAWN_ROUTER: '1'" in ci
@@ -85,9 +85,10 @@ test('the component CSS still matches the constants this file tests', async () =
const url = await import('node:url');
const here = url.fileURLToPath(new URL('.', import.meta.url));
// The test runs from .test-build, so resolve the source next to it by name.
// Both separators: on Windows `here` is D:\...\.test-build\..., and a '/'-only replace found nothing (CI, 2026-09-06).
const candidates = [
here + 'AgentChat.tsx',
here.replace('/.test-build/', '/src/') + 'AgentChat.tsx',
here.replace(/([\\/])\.test-build([\\/])/, '$1src$2') + 'AgentChat.tsx',
];
const path = candidates.find((p) => fs.existsSync(p));
assert.ok(path, `could not locate AgentChat.tsx from ${here}`);