## Implementation Plan: Fix XML-RPC Bridge Crash-Restart Loop ### Changes Required **1. `devplacepy/services/xmlrpc/__init__.py`** - **Line 32–37 (`_spawn()`):** Change `stderr=subprocess.DEVNULL` to `stderr=subprocess.PIPE`. After `self._process = subprocess.Popen(…)`, add a post-spawn health check: - Sleep 0.5 seconds, then poll. - If process is dead (`poll() is not None`), read the captured stderr (`.communicate()[1]`), log the stderr and return code, and set a flag to indicate spawn failure. - If alive, attempt a simple socket connection to `127.0.0.1:XMLRPC_PORT` with a 1-second timeout; if it fails, log warning but do not declare failure (allow for eventual binding). - Change the log message from `"Forking XML-RPC server started"` to `"Forking XML-RPC server spawning (pid …)"` and only log `"started"` after the health check passes (or after a 1-second wait without death). - **Line 38–41:** Move the original log statement to after the health check passes. If health check fails, do not log “started”. - **Line 43–54 (`_terminate()`):** No change needed. - **Line 63–68 (`run_once()`):** When `self._process.poll() is not None`, log the return code (`self._process.returncode`) and any captured stderr from the spawn (store `self._spawn_stderr` in `_spawn()`). Use `self._process.communicate()` if stderr not yet read, but careful: `communicate()` waits for process to finish – since process is already dead, it’s safe. Store stderr in a field like `self._last_stderr` during `_spawn()`. **2. `devplacepy/services/xmlrpc/server.py`** - **Lines 112–120 (`main()`):** Replace the existing `try/except KeyboardInterrupt` with: ```python try: build_server().serve_forever() except Exception as e: logging.exception("XML-RPC server crashed with unhandled exception") sys.exit(1) ``` Ensure `logging` is imported and configured (add `logging.basicConfig(level=logging.ERROR)` at top of `main()` if not already). **3. Testing & Verification** - The existing test file `tests/api/xmlrpc/index.py` uses a separate port and manages the subprocess directly – no changes needed. - Run the project’s verification commands inside a virtual environment or with `--break-system-packages` if necessary: ```bash pip install --break-system-packages -e '.[dev]' -q && make test-unit pip install --break-system-packages -q ruff && ruff check . ``` ### Definition of Done - [ ] All source changes described above are applied and committed. - [ ] `make test-unit` completes with exit code 0 (all tests pass, including XML-RPC bridge test). - [ ] `ruff check .` reports no errors. - [ ] Manual review confirms: - `stderr` is captured (no longer sent to `/dev/null`). - A post-spawn health check exists (waits for process to stay alive and port to open). - The supervisor logs the exit code and any captured stderr when a process dies. - `server.py`’s `main()` catches all exceptions, logs them, and exits with code 1. - [ ] No regression in existing functionality (functional tests cover all XML-RPC methods).