From 08fd58ccc0bee7c5f26649aedc162ff9459f9f19 Mon Sep 17 00:00:00 2001 From: mfowler Date: Sat, 4 Jul 2026 17:47:23 +0000 Subject: [PATCH] =?UTF-8?q?feat(pipeline):=20push-on-retire=20=E2=80=94=20?= =?UTF-8?q?no=20stranded=20commits=20when=20a=20stage=20completes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pipeline retires a stage the instant its completion marker appears; if the agent wrote the marker before its final 'git push' landed, its last commits were stranded locally (observed: a review stage left 7 unpushed commits). Now, before retiring a COMPLETED stage, the watchdog does a best-effort 'git push origin HEAD:main' in that stage's dir (never raises; 90s timeout). Guarantees the deliverable reaches the remote. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01UWTdUq2bsic7JZGqJp3nD6 --- agents.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/agents.py b/agents.py index c1751eb..64808c8 100755 --- a/agents.py +++ b/agents.py @@ -977,6 +977,25 @@ def _pipeline_marker(cfg, stage): p = Path(stage["done"]) return p if p.is_absolute() else Path(cfg["project_dir"]) / stage["done"] +def _pipeline_push_on_retire(agent): + """Best-effort `git push` in a completing stage's dir before it's retired, so its final commits + aren't stranded locally if it wrote its completion marker before its last push landed (a race: + the pipeline retires a stage the instant its marker appears). Never raises; logs the outcome.""" + d = agent.get("dir") + if not d or not (Path(d) / ".git").exists(): + return + try: + r = subprocess.run(["git", "-C", d, "push", "origin", "HEAD:main"], + env={**os.environ, "GIT_SSH_COMMAND": "ssh -o BatchMode=yes"}, + capture_output=True, text=True, timeout=90) + if r.returncode == 0: + log(f"pipeline: pushed {agent['name']!r} before retiring (no stranded commits)") + elif "up-to-date" not in (r.stderr + r.stdout).lower(): + log(f"pipeline: push-on-retire for {agent['name']!r} nonzero (non-fatal): " + f"{(r.stderr or r.stdout).strip().splitlines()[-1:] }") + except Exception as e: + log(f"pipeline: push-on-retire for {agent['name']!r} failed (non-fatal): {e}") + def pipeline_check(cfg): """Reconcile the [pipeline]: run the first stage whose completion marker is absent, retire every other pipeline agent. Returns the ACTIVE stage's agent dict (so the watchdog stall/heal-watches it), @@ -1000,6 +1019,10 @@ def pipeline_check(cfg): log(f"pipeline: advancing → start stage {ag['name']!r}") start_agent(cfg, ag, force=True) elif alive: + # Retiring a non-active stage. If it's a COMPLETED stage (its marker exists), push its repo + # first so final commits written just before the marker aren't stranded locally. + if _pipeline_marker(cfg, st).exists(): + _pipeline_push_on_retire(ag) log(f"pipeline: retire stage {ag['name']!r} (complete or not yet active)") kill_session(ag["session"]) if active is None: