plan: queue proxy and ghost follow-up phases

This commit is contained in:
autonomic-bot
2026-06-12 15:56:03 +00:00
parent a186f23b37
commit dea6359bcd
5 changed files with 262 additions and 17 deletions
+24 -17
View File
@@ -152,6 +152,13 @@ def session_alive(name):
capture_output=True
).returncode == 0
def session_command(name):
r = subprocess.run(
["tmux", "display-message", "-p", "-t", name, "#{pane_current_command}"],
capture_output=True, text=True
)
return r.stdout.strip() if r.returncode == 0 else ""
def kill_session(name):
subprocess.run(["tmux", "kill-session", "-t", name], capture_output=True)
@@ -573,26 +580,21 @@ def stall_check():
# ── orchestrator healing ──────────────────────────────────────────────────────
def orchestrator_alive():
"""True only for the configured orchestrator tmux session.
Older versions treated any non-loop Claude process as an orchestrator, which meant idle
report/assistant sessions could mask a missing or stale orchestrator. The watchdog wakes
and heals ORCH_SESSION, so ORCH_SESSION is the only valid liveness signal here.
"""
True if an orchestrator process is running anywhere.
Conflict-safety: never launch a second orchestrator resuming the same session
(double-resume causes "thinking blocks cannot be modified" crashes).
"""
for line in subprocess.run("pgrep -x claude || true", shell=True,
capture_output=True, text=True).stdout.splitlines():
pid = line.strip()
if not pid:
continue
try:
cmdline = Path(f"/proc/{pid}/cmdline").read_bytes().decode(errors="replace").replace("\0", " ")
# Skip the loop sessions and the upgrader — they're not the orchestrator.
if re.search(r"--remote-control\s+'?cc-ci-(builder|adv|upgrader)'?", cmdline):
continue
return True
except Exception:
pass
return session_alive(ORCH_SESSION)
def orchestrator_backend_mismatch():
cmd = session_command(ORCH_SESSION)
if not cmd:
return False
expected = "opencode" if BACKEND == "opencode" else "claude"
return cmd != expected
def heal_orchestrator():
if not WATCH_ORCHESTRATOR:
return
@@ -601,6 +603,11 @@ def heal_orchestrator():
if orchestrator_alive():
if session_alive(ORCH_SESSION):
if orchestrator_backend_mismatch():
log(f"orchestrator ({ORCH_SESSION}) is {session_command(ORCH_SESSION)!r}, expected {BACKEND} — kill + restart")
kill_session(ORCH_SESSION)
subprocess.run([ORCH_LAUNCHER, "start"], capture_output=True)
return
pane = capture_pane(ORCH_SESSION, 25)
if ACTIVE_RE.search(pane):
return