Cards adopt an open PR that already closes them (review and land it instead of dispatching a worker) #110

Merged
time merged 4 commits from time/card-93 into main 2026-10-18 04:39:49 +00:00
Collaborator

Round 2, answering haiku's review.

Fixed (blocking): the adoption guard now accepts needs-done and, in live mode, would-dispatch. A dry-run tick leaves a would-dispatch beat, and the old state is None guard skipped adoption when live mode started. The worker branch then launched a duplicate build. In dry-run, a card already at would-dispatch doesn't post its adoption note again.

Test: 13b now covers dry run, then a second dry run (no repeat note), then live. The live tick adopts the PR, posts an adopted pr beat, posts a review, and launches no worker.

Also tightened: PR: #N in a card body must now be the only content on its line, so quoted or example text like PR: #7 is an example doesn't adopt. A test covers it.

Not changed (said here as asked): adoptable() takes any open PR whose body says Closes #N, whatever its author or branch. That is what the card asks for, since Codex and seat PRs don't use time/card-N branches. The TIME_AUTOMERGE gate still applies. The reviewer must Approve and checks must be green before anything lands. Only a user with write access can label a card ready.

Checked: python3 .github/time/test_time.py exits 0, and "ALL OK 13b" prints. The CHANGELOG line is already under "### Added".

Closes #93

worker: Claude Sonnet (Anthropic) via Time

Round 2, answering haiku's review. **Fixed (blocking):** the adoption guard now accepts `needs-done` and, in live mode, `would-dispatch`. A dry-run tick leaves a `would-dispatch` beat, and the old `state is None` guard skipped adoption when live mode started. The worker branch then launched a duplicate build. In dry-run, a card already at `would-dispatch` doesn't post its adoption note again. **Test:** 13b now covers dry run, then a second dry run (no repeat note), then live. The live tick adopts the PR, posts an adopted `pr` beat, posts a review, and launches no worker. **Also tightened:** `PR: #N` in a card body must now be the only content on its line, so quoted or example text like `PR: #7 is an example` doesn't adopt. A test covers it. **Not changed (said here as asked):** `adoptable()` takes any open PR whose body says `Closes #N`, whatever its author or branch. That is what the card asks for, since Codex and seat PRs don't use `time/card-N` branches. The `TIME_AUTOMERGE` gate still applies. The reviewer must Approve and checks must be green before anything lands. Only a user with write access can label a card `ready`. **Checked:** `python3 .github/time/test_time.py` exits 0, and "ALL OK 13b" prints. The CHANGELOG line is already under "### Added". Closes #93 <sub>worker: Claude Sonnet (Anthropic) via Time</sub>
time added 2 commits 2026-10-18 02:06:33 +00:00
card #93: kept CHANGELOG.md, .github/time/time.py, .github/time/test_time.py (generated)
time/worker-run cd .github/time && python3 - <<'EOF' p='time.py' s=open(p).read() s=s.replace('''def pr_diff(number:
6eac9461c0
card #93: kept .github/time/time.py, .github/time/test_time.py (generated)
ci / test (pull_request) Successful in 35s
time/worker-run cd .github/time && python3 - <<'E' s=open('time.py').read() s=s.replace(''' if state is N
e4e5e0713d

Object

The adoption path can't fire on the flow Time normally runs. Dry-run posts a would-dispatch beat, and the adoption guard only accepts state is None.

  • .github/time/time.py:1113: if state is None and not revision and (adopt := adoptable(prs, card)). A card that a dry-run tick has already seen has state == "would-dispatch". When live mode starts, adoption is skipped and the existing branch at about line 1131 (state in (None, "needs-done", "would-dispatch")) launches a worker. That is the duplicate build the card is meant to prevent. The card asks for "no real beats (state None)", and would-dispatch is a dry-run marker, so the guard should also accept would-dispatch (and arguably needs-done, which means no dispatch happened yet).
  • .github/time/test_time.py (13b): every adoption case starts from an empty card, so none covers dry-run followed by live. A check of run(dry_world) then run(same_world, live) would have caught this.

Smaller points, not blocking:

  • .github/time/time.py:1000-1003: adoptable() returns the first open PR whose body merely contains Closes #N, regardless of author or branch. Under TIME_AUTOMERGE=on any such PR, including an unrelated human PR, gets reviewed and can be landed. The reviewer gate still applies, but say so in the PR description or restrict adoption to the card's expected branch convention.
  • .github/time/time.py:995: the PR: #N regex matches anywhere a line starts with PR:, including quoted or example text in the card body. Consider requiring it to be the only content on its line.
  • The review() signature and the comments[7][-1] assertion depend on code not in this diff. I assumed they match existing usage.

Fix the guard to include would-dispatch, add a dry-run→live test, and this is ready.

review by haiku · claude-haiku-5-5 · via Time

**Object** The adoption path can't fire on the flow Time normally runs. Dry-run posts a `would-dispatch` beat, and the adoption guard only accepts `state is None`. - `.github/time/time.py:1113`: `if state is None and not revision and (adopt := adoptable(prs, card))`. A card that a dry-run tick has already seen has `state == "would-dispatch"`. When live mode starts, adoption is skipped and the existing branch at about line 1131 (`state in (None, "needs-done", "would-dispatch")`) launches a worker. That is the duplicate build the card is meant to prevent. The card asks for "no real beats (state None)", and `would-dispatch` is a dry-run marker, so the guard should also accept `would-dispatch` (and arguably `needs-done`, which means no dispatch happened yet). - `.github/time/test_time.py` (13b): every adoption case starts from an empty card, so none covers dry-run followed by live. A check of `run(dry_world)` then `run(same_world, live)` would have caught this. Smaller points, not blocking: - `.github/time/time.py:1000-1003`: `adoptable()` returns the first open PR whose body merely contains `Closes #N`, regardless of author or branch. Under `TIME_AUTOMERGE=on` any such PR, including an unrelated human PR, gets reviewed and can be landed. The reviewer gate still applies, but say so in the PR description or restrict adoption to the card's expected branch convention. - `.github/time/time.py:995`: the `PR: #N` regex matches anywhere a line starts with `PR:`, including quoted or example text in the card body. Consider requiring it to be the only content on its line. - The `review()` signature and the `comments[7][-1]` assertion depend on code not in this diff. I assumed they match existing usage. Fix the guard to include `would-dispatch`, add a dry-run→live test, and this is ready. <sub>review by haiku · claude-haiku-5-5 · via Time</sub>
time added the needs-human label 2026-10-18 04:15:55 +00:00
time removed the needs-human label 2026-10-18 04:26:45 +00:00
gitea-actions Bot added 1 commit 2026-10-18 04:27:02 +00:00
card #93: kept .github/time/time.py, .github/time/test_time.py (generated)
time/worker-run python3 .github/time/test_time.py >/tmp/o 2>&1; echo $?; grep -c "ALL OK 13b" /tmp/o; git diff HEAD
5a18288853
time changed title from Cards adopt an open PR that already closes them: review and land it instead of dispatching a worker to Cards adopt an open PR that already closes them (review and land it instead of dispatching a worker) 2026-10-18 04:27:11 +00:00

Approve, with two things to confirm before landing.

  • .github/time/time.py:991-1011 (adoptable): closed and merged PRs are filtered out on both paths, the Closes #N path and the PR: #N path. The PR: #N regex is anchored to a line of its own (^\s*PR:\s*#(\d+)\s*$), so quoted text doesn't adopt. The fallback GET only runs when the number isn't in the listed PRs, and a failed GET returns None.
  • .github/time/time.py:1113-1129 (tick): the guard is state in (None, "needs-done"), or would-dispatch in live mode. A dry-run would-dispatch beat therefore doesn't repeat the note on the next dry tick, and going live adopts instead of launching a duplicate worker. Adoption also skips any card with a pending revision.
  • .github/time/time.py:1119-1120,1126: a cursor reviewer still increments launches_today. The card says "no launch is counted," so either the card or the code should change. The new test only covers the venice reviewer, so it doesn't catch this. This is not a blocker, but the wording should match.
  • Risk, not a blocker: adoptable() accepts any open PR whose body says Closes #N, whatever its author. The card asks for this, and the TIME_AUTOMERGE gate, reviewer Approve, green checks and the ready label (write access only) still stand between a PR and a merge. A stranger's PR that closes a ready card can still get a review, so the protected-path default is the backstop.
  • Confirm: the head commit's statuses show only time/worker-run: success. The card's finish line also requires CI green and the test script green. I can't see either from here, so I haven't verified that test_time.py passes, including 13b. Please confirm the test run before merging.
  • The CHANGELOG line under "### Added" matches the behaviour.

review by haiku · claude-haiku-5-5 · via Time

**Approve**, with two things to confirm before landing. - `.github/time/time.py:991-1011` (`adoptable`): closed and merged PRs are filtered out on both paths, the `Closes #N` path and the `PR: #N` path. The `PR: #N` regex is anchored to a line of its own (`^\s*PR:\s*#(\d+)\s*$`), so quoted text doesn't adopt. The fallback GET only runs when the number isn't in the listed PRs, and a failed GET returns None. - `.github/time/time.py:1113-1129` (`tick`): the guard is `state in (None, "needs-done")`, or `would-dispatch` in live mode. A dry-run `would-dispatch` beat therefore doesn't repeat the note on the next dry tick, and going live adopts instead of launching a duplicate worker. Adoption also skips any card with a pending revision. - `.github/time/time.py:1119-1120,1126`: a `cursor` reviewer still increments `launches_today`. The card says "no launch is counted," so either the card or the code should change. The new test only covers the venice reviewer, so it doesn't catch this. This is not a blocker, but the wording should match. - Risk, not a blocker: `adoptable()` accepts any open PR whose body says `Closes #N`, whatever its author. The card asks for this, and the `TIME_AUTOMERGE` gate, reviewer Approve, green checks and the `ready` label (write access only) still stand between a PR and a merge. A stranger's PR that closes a `ready` card can still get a review, so the protected-path default is the backstop. - **Confirm:** the head commit's statuses show only `time/worker-run: success`. The card's finish line also requires CI green and the test script green. I can't see either from here, so I haven't verified that `test_time.py` passes, including 13b. Please confirm the test run before merging. - The CHANGELOG line under "### Added" matches the behaviour. <sub>review by haiku · claude-haiku-5-5 · via Time</sub>
teahubxyz added 1 commit 2026-10-18 04:35:43 +00:00
#116 and this PR both added their line right under "### Added"; git sees two different inserts at one point and refuses to
merge. The branch now carries main's line where main put it and its own line at the end of the list.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017K4vyY3yUD3GsvoZaAtKkD
time merged commit 7a471e39ca into main 2026-10-18 04:39:49 +00:00
time deleted branch time/card-93 2026-10-18 04:39:50 +00:00
Sign in to join this conversation.