diff --git a/.claude/skills/upgrade-all/SKILL.md b/.claude/skills/upgrade-all/SKILL.md index ca2bf70..0582b73 100644 --- a/.claude/skills/upgrade-all/SKILL.md +++ b/.claude/skills/upgrade-all/SKILL.md @@ -117,9 +117,10 @@ End with the report path and a reminder that **nothing was merged**. ## 6. Launch the public Recipe Report Once the summary is written, kick off the weekly public report (its own agent, separate model — default -opus): `python3 /srv/cc-ci/cc-ci-plan/launch-report.py start`. It runs `/recipe-report`, reviews this -run + the live recipe/PR state, and publishes to https://report.ci.commoninternet.net. Fire-and-forget -— it runs independently; you can then go idle. +opus): **`python3 /srv/cc-ci/cc-ci-plan/launch-report.py fresh`** (use `fresh`, not `start` — the report +is a one-shot and must always run a NEW session for THIS week, even if a previous report session is +still around). It runs `/recipe-report`, reviews this run + the live recipe/PR state, and publishes to +https://report.ci.commoninternet.net. Fire-and-forget — it runs independently; you can then go idle. ## Safety / coordination (this matters — shared host with the build loops) - **Sequential is the default for a reason.** Recipe deploys are **stateful on the shared Swarm** and diff --git a/cc-ci-plan/launch-report.py b/cc-ci-plan/launch-report.py index def3347..d30f363 100755 --- a/cc-ci-plan/launch-report.py +++ b/cc-ci-plan/launch-report.py @@ -32,6 +32,7 @@ def die(m): log(f"ERROR: {m}"); sys.exit(1) def _sh(c): return subprocess.run(c, capture_output=True, text=True) def session_alive(): return _sh(["tmux", "has-session", "-t", SESSION]).returncode == 0 def kill_session(): subprocess.run(["tmux", "kill-session", "-t", SESSION], capture_output=True) +def _busy(): return "esc to interrupt" in _sh(["tmux", "capture-pane", "-pt", SESSION]).stdout def build_kickoff(date): @@ -56,9 +57,11 @@ def start(mode, date): die("tmux not found") Path(LOG_DIR).mkdir(parents=True, exist_ok=True) if session_alive(): - if mode == "use-or-create": - log(f"{SESSION} already running — leaving it"); return - log(f"{SESSION} exists — killing (fresh)"); kill_session(); time.sleep(1) + # The report is a one-shot. Leave the session ONLY if it's actively producing a report; + # an idle/leftover session (e.g. last week's, gone idle) is killed so a new run starts. + if mode == "use-or-create" and _busy(): + log(f"{SESSION} busy with a report — leaving it"); return + log(f"{SESSION} exists (idle/leftover) — killing first"); kill_session(); time.sleep(1) kf = Path(LOG_DIR) / f".kickoff-{SESSION}.txt" kf.write_text(build_kickoff(date))