From adae8556753079ce0bbc991b7a0339002d2046c1 Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 11 Oct 2026 12:53:40 -0700 Subject: [PATCH] landing: verdict window from PR creation; worker test runs become commit statuses GitHub skips pull_request workflows for commits made with the Actions token, so a Venice worker's own test run is recorded as a commit status and counted as the PR's check. A verdict posted in the same beat as the pr beat now counts. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01UpT7eWzeLhRwuYmsSQnbRb --- .github/time/test_time.py | 15 ++++++++++++++- .github/time/time.py | 20 ++++++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/.github/time/test_time.py b/.github/time/test_time.py index e0c00c6..4a1dbe3 100644 --- a/.github/time/test_time.py +++ b/.github/time/test_time.py @@ -80,6 +80,9 @@ class World: return d["pr"] if "/check-runs" in path: return {"check_runs": next(d.get("checks", []) for d in self.gh_pr.values() if d["pr"]["head"]["sha"] in path)} + if path.endswith("/status"): + return {"statuses": next(d.get("statuses", []) for d in self.gh_pr.values() if d["pr"]["head"]["sha"] in path)} + if "/statuses/" in path and method == "POST": self.statuses = getattr(self, "statuses", []) + [body]; return {} if "/issues?state=open" in path: lab = path.split("labels=")[1].split("&")[0] return [{"number": i["number"], "title": i.get("title", ""), "body": i.get("body", ""), "state": "open"} @@ -199,7 +202,7 @@ assert "couldn't find cards" in w.posted[0][1] and w.issues[1]["labels"] == [] # 13 landing: Approve from a trusted reviewer + green checks -> squash merge, card closed, next card dispatched def pr_world(verdict_body="**Approve** looks right", assoc="NONE", bot=True, checks=None, files=None, extra=None): - prd = {"number": 7, "state": "open", "merged_at": None, "head": {"sha": "abc"}, "html_url": "u"} + prd = {"number": 7, "state": "open", "merged_at": None, "head": {"sha": "abc"}, "html_url": "u", "created_at": iso(2)} issues = {2: {"number": 2, "title": "next", "body": "Depends on #1\n\nDONE: d", "state": "open", "labels": ["queued"]}} cm = {1: [beat("pr", 1, pr=7)], 7: [{"created_at": iso(), "author_association": assoc, "user": {"type": "Bot" if bot else "User"}, "body": verdict_body}]} @@ -249,3 +252,13 @@ assert "PR #42" in w2.posted[0][1] w3 = run(World([card(1, D)], {1: [beat("would-dispatch")]}), {"TIME_MODE": "live", "TIME_WORKER": "venice:kimi"}) assert "VENICE_API_KEY" in w3.posted[-1][1] print("ALL OK 4") + +# 15 a commit status from the worker's own test run counts as the check +w = pr_world(checks=[]); w.gh_pr[7]["statuses"] = [{"state": "success", "context": "time/worker-run"}] +w = run(w, {**K, "TIME_AUTOMERGE": "on"}); assert any(c[0] == "PUT" for c in w.calls), "status should count as a green check" +w = pr_world(checks=[]); w.gh_pr[7]["statuses"] = [{"state": "failure", "context": "time/worker-run"}] +w = run(w, {**K, "TIME_AUTOMERGE": "on"}); assert not any(c[0] == "PUT" for c in w.calls) +# a verdict posted in the same run as the `pr` beat still counts (window starts at PR creation) +w = pr_world(); w.comments[1] = [beat("pr", 0, pr=7)] +w = run(w, {**K, "TIME_AUTOMERGE": "on"}); assert any(c[0] == "PUT" for c in w.calls) +print("ALL OK 5") diff --git a/.github/time/time.py b/.github/time/time.py index e885e86..3b2eaf5 100644 --- a/.github/time/time.py +++ b/.github/time/time.py @@ -309,16 +309,19 @@ def touches_protected(pr_number: int) -> list[str]: def checks(sha: str) -> str: """pass | pending | fail | none, over every check run on the PR head.""" runs = gh("GET", f"/repos/{REPO}/commits/{sha}/check-runs?per_page=100").get("check_runs", []) - if not runs: + states = gh("GET", f"/repos/{REPO}/commits/{sha}/status").get("statuses", []) # commit statuses count too + if not runs and not states: return "none" - if any(r["status"] != "completed" for r in runs): + if any(r["status"] != "completed" for r in runs) or any(st["state"] == "pending" for st in states): return "pending" - return "pass" if all(r["conclusion"] in ("success", "neutral", "skipped") for r in runs) else "fail" + ok = all(r["conclusion"] in ("success", "neutral", "skipped") for r in runs) and all(st["state"] == "success" for st in states) + return "pass" if ok else "fail" def land(card: dict, last: dict) -> None: """Move a card whose PR is up toward merged. Silent while nothing has changed.""" n, pr = card["number"], gh("GET", f"/repos/{REPO}/pulls/{last['pr']}") + since = pr["created_at"] # any verdict after the PR exists counts; a stale beat time would hide same-run reviews if pr.get("merged_at"): if card.get("state", "open") == "open": gh("PATCH", f"/repos/{REPO}/issues/{n}", {"state": "closed"}) @@ -326,7 +329,7 @@ def land(card: dict, last: dict) -> None: return if pr["state"] == "closed" or (os.environ.get("TIME_AUTOMERGE") or "off").strip() != "on": return - v = verdict(pr, last["at"]) + v = verdict(pr, since) if v is None: return if v == "object": @@ -393,7 +396,7 @@ WORKER_RULES = """You are the worker for card #{n} in {repo}: "{title}". You have hands: list_files, read_file, write_file (one file per call, full content), run (shell on a checkout of your branch), open_pr. Work on the branch `{branch}`; everything you write lands there. Keep the change small and focused. -Read before you write. Run the project's tests or checks if any exist. When it is done and checked, call open_pr once +Read before you write. Always `run` the project's tests or checks before opening the PR (that run is the PR's check). When it is done and checked, call open_pr once with a short summary; the PR must close the card, and Time adds that line for you. Do not touch .github/workflows. The card's finish line is: DONE: {done}""" @@ -441,8 +444,13 @@ class Hands: self.checkout = None return f"wrote {a['path']}" if name == "run": - self.checkout = self.checkout or self._checkout() + self.checkout = self._checkout() # fresh: see the latest writes p = subprocess.run(["bash", "-lc", a["command"]], cwd=self.checkout, capture_output=True, text=True, timeout=600) + sha = subprocess.run(["git", "rev-parse", "HEAD"], cwd=self.checkout, capture_output=True, text=True).stdout.strip() + # GitHub does not run pull_request workflows for commits made with the Actions token, so the worker's own + # test run is recorded as a commit status; land() counts statuses as checks. + gh("POST", f"/repos/{REPO}/statuses/{sha}", {"state": "success" if p.returncode == 0 else "failure", + "context": "time/worker-run", "description": a["command"][:100]}) return f"exit {p.returncode}\n{(p.stdout + p.stderr)[-6000:]}" if name == "open_pr": pr = gh("POST", f"/repos/{REPO}/pulls", {"title": a["title"][:200], "head": self.branch, "base": default_branch(),