feat(pipeline): push-on-retire — no stranded commits when a stage completes
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UWTdUq2bsic7JZGqJp3nD6
This commit is contained in:
23
agents.py
23
agents.py
@ -977,6 +977,25 @@ def _pipeline_marker(cfg, stage):
|
|||||||
p = Path(stage["done"])
|
p = Path(stage["done"])
|
||||||
return p if p.is_absolute() else Path(cfg["project_dir"]) / 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):
|
def pipeline_check(cfg):
|
||||||
"""Reconcile the [pipeline]: run the first stage whose completion marker is absent, retire every
|
"""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),
|
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}")
|
log(f"pipeline: advancing → start stage {ag['name']!r}")
|
||||||
start_agent(cfg, ag, force=True)
|
start_agent(cfg, ag, force=True)
|
||||||
elif alive:
|
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)")
|
log(f"pipeline: retire stage {ag['name']!r} (complete or not yet active)")
|
||||||
kill_session(ag["session"])
|
kill_session(ag["session"])
|
||||||
if active is None:
|
if active is None:
|
||||||
|
|||||||
Reference in New Issue
Block a user