SPB Git

spb/ka-guardian

Public

fix(runner): watchdog de timeout indépendant du flux stdout

Le contrôle du deadline vivait dans la boucle for line in proc.stdout : il ne
s exécutait qu à l arrivée d une ligne. Un claude qui se tait (outil qui pend)
ou qui n exit jamais après son résultat laissait la mission « running » pour
toujours et bloquait le nœud (mission f9eb9d1b59c8 du 2026-08-24 : travail fini
à 10:38, processus zombie tué à la main à 15:46). Un thread watchdog tue
désormais le processus au deadline quoi qu il arrive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Aug 24, 2026) parent 33c1c36 Browse files

1 changed file +15 −0

Unified Split
modified runner/runner.py +15 −0
@@ -234,6 +234,21 @@ def run_mission(m: MissionIn) -> None:
234 result_text, cost, turns = "", None, None 234 result_text, cost, turns = "", None, None
235 spent_estimate = 0.0 235 spent_estimate = 0.0
236 deadline = time.time() + m.timeout_seconds 236 deadline = time.time() + m.timeout_seconds
237+
238+ def _watchdog() -> None:
239+ # Filet indépendant du flux stdout : le contrôle du deadline dans
240+ # la boucle de lecture ne s'exécute qu'à l'arrivée d'une ligne —
241+ # si claude se tait (outil qui pend) ou n'exit jamais après son
242+ # résultat (mission f9eb9d1b59c8 du 2026-08-24, zombie 5 h), la
243+ # mission restait « running » pour toujours et bloquait le nœud.
244+ while proc.poll() is None:
245+ if time.time() > deadline:
246+ proc.kill()
247+ post_event(m.callback_url, {"type": "error", "data": {"error": f"durée max atteinte ({m.timeout_seconds // 60} min) — session interrompue par le watchdog"}})
248+ return
249+ time.sleep(30)
250+
251+ threading.Thread(target=_watchdog, daemon=True).start()
237 with transcript.open("w") as tf: 252 with transcript.open("w") as tf:
238 for line in proc.stdout: # type: ignore[union-attr] 253 for line in proc.stdout: # type: ignore[union-attr]
239 tf.write(line) 254 tf.write(line)
240 255
Menu

Theme