| 3596 | process = self._process # Capture for closure |
| 3597 | |
| 3598 | async def read_port(): |
| 3599 | if not process or not process.stdout: |
| 3600 | raise RuntimeError("Process not started or stdout not available") |
| 3601 | while True: |
| 3602 | line = await loop.run_in_executor(None, process.stdout.readline) |
| 3603 | if not line: |
| 3604 | raise RuntimeError("CLI process exited before announcing port") |
| 3605 | |
| 3606 | line_str = line.decode() if isinstance(line, bytes) else line |
| 3607 | logger.debug("[CLI] %s", line_str.rstrip()) |
| 3608 | match = re.search(r"listening on port (\d+)", line_str, re.IGNORECASE) |
| 3609 | if match: |
| 3610 | self._runtime_port = int(match.group(1)) |
| 3611 | return |
| 3612 | |
| 3613 | try: |
| 3614 | port_wait_start = time.perf_counter() |