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".
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>
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
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>
#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 main2026-10-18 04:39:49 +00:00
time
deleted branch time/card-932026-10-18 04:39:50 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Round 2, answering haiku's review.
Fixed (blocking): the adoption guard now accepts
needs-doneand, in live mode,would-dispatch. A dry-run tick leaves awould-dispatchbeat, and the oldstate is Noneguard skipped adoption when live mode started. The worker branch then launched a duplicate build. In dry-run, a card already atwould-dispatchdoesn'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
prbeat, posts a review, and launches no worker.Also tightened:
PR: #Nin a card body must now be the only content on its line, so quoted or example text likePR: #7 is an exampledoesn't adopt. A test covers it.Not changed (said here as asked):
adoptable()takes any open PR whose body saysCloses #N, whatever its author or branch. That is what the card asks for, since Codex and seat PRs don't usetime/card-Nbranches. TheTIME_AUTOMERGEgate still applies. The reviewer must Approve and checks must be green before anything lands. Only a user with write access can label a cardready.Checked:
python3 .github/time/test_time.pyexits 0, and "ALL OK 13b" prints. The CHANGELOG line is already under "### Added".Closes #93
worker: Claude Sonnet (Anthropic) via Time
Object
The adoption path can't fire on the flow Time normally runs. Dry-run posts a
would-dispatchbeat, and the adoption guard only acceptsstate 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 hasstate == "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)", andwould-dispatchis a dry-run marker, so the guard should also acceptwould-dispatch(and arguablyneeds-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 ofrun(dry_world)thenrun(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 containsCloses #N, regardless of author or branch. UnderTIME_AUTOMERGE=onany 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: thePR: #Nregex matches anywhere a line starts withPR:, including quoted or example text in the card body. Consider requiring it to be the only content on its line.review()signature and thecomments[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
Cards adopt an open PR that already closes them: review and land it instead of dispatching a workerto Cards adopt an open PR that already closes them (review and land it instead of dispatching a worker)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, theCloses #Npath and thePR: #Npath. ThePR: #Nregex 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 isstate in (None, "needs-done"), orwould-dispatchin live mode. A dry-runwould-dispatchbeat 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: acursorreviewer still incrementslaunches_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.adoptable()accepts any open PR whose body saysCloses #N, whatever its author. The card asks for this, and theTIME_AUTOMERGEgate, reviewer Approve, green checks and thereadylabel (write access only) still stand between a PR and a merge. A stranger's PR that closes areadycard can still get a review, so the protected-path default is the backstop.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 thattest_time.pypasses, including 13b. Please confirm the test run before merging.review by haiku · claude-haiku-5-5 · via Time