From 79403188f64784a9ea7aee3ec1ea443bbf5bdc8f Mon Sep 17 00:00:00 2001 From: Jeffrey Phillips Freeman Date: Thu, 2 Apr 2026 17:37:12 +0000 Subject: [PATCH] build(agents): add stale supervisor cleanup at startup to prevent duplicates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the product-builder is interrupted mid-run (Ctrl+C, crash, timeout), supervisor sessions launched via prompt_async survive independently on the OpenCode server. Restarting the product-builder without cleanup creates duplicate supervisors — 22 agents competing for the same work, causing duplicate PR reviews, conflicting git pushes, and wasted resources. Added Phase C.0 (runs before planning or launching) that: 1. Queries GET /session for all server sessions 2. Filters for sessions with titles starting with "[CA-AUTO] supervisor:" 3. Aborts any that are still active via POST /session/:id/abort 4. Deletes the stale sessions via DELETE /session/:id 5. Cleans up the /tmp/ca-supervisor-sessions.env tracking file 6. Logs the cleanup count to the session state issue Also changed the supervisor session title convention from "supervisor: " to "[CA-AUTO] supervisor: " to reliably distinguish product-builder-managed sessions from user-created ones. --- .opencode/agents/product-builder.md | 51 ++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/.opencode/agents/product-builder.md b/.opencode/agents/product-builder.md index f3d8ae78..e74c75f5 100644 --- a/.opencode/agents/product-builder.md +++ b/.opencode/agents/product-builder.md @@ -359,6 +359,55 @@ milestones = list of all milestones to complete (ordered) ref_summary = result from ca-ref-reader SERVER = "http://localhost:4096" +# ── PHASE C.0: Clean Up Stale Supervisor Sessions ─────────────── +# If the product-builder was previously interrupted (Ctrl+C, crash, +# session timeout, etc.), old supervisor sessions launched via +# prompt_async may still be running on the server. They are +# independent sessions that survive the product-builder's death. +# +# We MUST find and abort them before launching fresh supervisors, +# otherwise there will be duplicate supervisors competing for the +# same work — duplicate PR reviews, conflicting git pushes, etc. +# +# Identification: All supervisor sessions have titles starting with +# "[CA-AUTO] supervisor:". This prefix is unique to product-builder- +# managed supervisors and will not match user-created sessions. + +# Step 1: Query all sessions from the server +ALL_SESSIONS=$(bash("curl -s ${SERVER}/session", timeout=30000)) + +# Step 2: Extract sessions whose title starts with "[CA-AUTO] supervisor:" +# Use python3 to parse JSON and filter: +STALE_IDS=$(bash("echo '${ALL_SESSIONS}' | python3 -c \" +import sys, json +sessions = json.loads(sys.stdin.read()) +for s in sessions: + if s.get('title', '').startswith('[CA-AUTO] supervisor:'): + print(s['id']) +\"", timeout=30000)) + +# Step 3: Abort and delete each stale session +stale_count = 0 +for session_id in STALE_IDS (one per line): + # Abort if still running + bash("curl -s -X POST ${SERVER}/session/${session_id}/abort", timeout=15000) + # Delete the session entirely to avoid clutter + bash("curl -s -X DELETE ${SERVER}/session/${session_id}", timeout=15000) + stale_count += 1 + +# Step 4: Clean up tracking file from previous run +bash("rm -f /tmp/ca-supervisor-sessions.env", timeout=5000) + +# Step 5: Log cleanup results +if stale_count > 0: + invoke ca-session-persister with: + checkpoint: "Phase C.0: Cleaned up stale supervisor + sessions from a previous interrupted run. + All old supervisors aborted and deleted." +else: + # No stale sessions — first run or clean shutdown previously. + pass + # ── PHASE C.1: Planning (if needed) ───────────────────────────── # If there are milestones with no issues yet, plan them first. # This is the ONLY step that uses the Task tool (one-shot planners). @@ -411,7 +460,7 @@ function launch_supervisor(agent_name, display_name, prompt_text): # Step 1: Create a session SESSION_ID=$(curl -s -X POST "${SERVER}/session" \ -H "Content-Type: application/json" \ - -d "{\"title\": \"supervisor: ${display_name}\"}" \ + -d "{\"title\": \"[CA-AUTO] supervisor: ${display_name}\"}" \ | python3 -c "import sys,json; print(json.loads(sys.stdin.read())['id'])") # Step 2: Fire-and-forget launch via prompt_async