From e5466491c7af5cf57ae4ec0771cb344385959db5 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 24 Mar 2026 11:41:58 -0700 Subject: [PATCH] fix(rdesign): auto-restart Scribus when runner socket is missing The bridge's /start endpoint was returning "already running" even when the runner script had crashed (socket gone). Now kills zombie Scribus and restarts. Agent route also verifies runner connectivity after start. Co-Authored-By: Claude Opus 4.6 --- docker/scribus-novnc/bridge/server.py | 7 ++++++- modules/rdesign/design-agent-route.ts | 22 ++++++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/docker/scribus-novnc/bridge/server.py b/docker/scribus-novnc/bridge/server.py index 6747c9d..dc7bca6 100644 --- a/docker/scribus-novnc/bridge/server.py +++ b/docker/scribus-novnc/bridge/server.py @@ -96,7 +96,12 @@ def start_scribus(): global _scribus_proc if _scribus_proc and _scribus_proc.poll() is None: - return jsonify({"ok": True, "message": "Scribus already running", "pid": _scribus_proc.pid}) + if os.path.exists(SOCKET_PATH): + return jsonify({"ok": True, "message": "Scribus already running", "pid": _scribus_proc.pid}) + # Process alive but runner socket missing — kill and restart + _scribus_proc.kill() + _scribus_proc.wait(timeout=5) + _scribus_proc = None # Clean up stale socket if os.path.exists(SOCKET_PATH): diff --git a/modules/rdesign/design-agent-route.ts b/modules/rdesign/design-agent-route.ts index 6b566e6..f7a0ba8 100644 --- a/modules/rdesign/design-agent-route.ts +++ b/modules/rdesign/design-agent-route.ts @@ -61,7 +61,7 @@ async function bridgeState(): Promise { } } -/** Start Scribus if not running. */ +/** Start Scribus if not running, verify runner is connected. */ async function ensureScribusRunning(): Promise { const headers: Record = { "Content-Type": "application/json" }; if (BRIDGE_SECRET) headers["X-Bridge-Secret"] = BRIDGE_SECRET; @@ -72,7 +72,25 @@ async function ensureScribusRunning(): Promise { headers, signal: AbortSignal.timeout(20_000), }); - return await res.json(); + const result = await res.json(); + if (result.error) return result; + + // Verify runner is actually connected by checking state + const stateRes = await fetch(`${SCRIBUS_BRIDGE_URL}/api/scribus/state`, { + headers, + signal: AbortSignal.timeout(10_000), + }); + const state = await stateRes.json(); + if (!state.error) return result; + + // Runner not connected — force restart by calling start again + // (server.py now kills zombie Scribus when socket is missing) + const retryRes = await fetch(`${SCRIBUS_BRIDGE_URL}/api/scribus/start`, { + method: "POST", + headers, + signal: AbortSignal.timeout(20_000), + }); + return await retryRes.json(); } catch (e: any) { return { error: `Bridge unreachable: ${e.message}` }; }