Conversation
Not for merge. tests/test_worktree_gh.py needs a real, open PR to read; on a push to main there is no PR under test, and PB_REQUIRE_GH=1 correctly fails rather than silently skipping. vars.PB_GH_FIXTURE_PR points here so main keeps real-GitHub coverage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
requires_gh tier probe — idempotent marked comment (safe to leave; updated in place). |
This was referenced Aug 31, 2026
Merged
mabry1985
added a commit
that referenced
this pull request
Sep 10, 2026
Adversarial review of #437 found seven defects; each has a test that fails on the previous head (8c45cd7) and passes here. 1. Ready-gate bypass: eligibility used the board STATE, and `blocked` is a flag on a lane, so a blocked backlog card (never Ready) or a blocked epic went straight into review. It is now the lane underneath the block (status without the flag) -- ready/in_progress, or in_review under a block -- and only an issue_type `feature`. 2. Re-attaching a blocked card's OWN PR lifted the block (e.g. "review findings persist -- needs human review") and re-armed the gate without a human. A card already tracking the PR is a no-op in review and refused anywhere else; the refusal names the block and that unblocking is a deliberate board_unblock_feature. 3. The health sweep moved cards on reads taken before a gh round-trip, outside the claim lock the attach writes under: the orphan pass and the blocked self-heal requeued an attach that had just landed. Both now re-read the card under the same lock and act only if it is still what they read (positive evidence of a move skips). When the sweep adopts the PR first, the attach reports already_attached, arms review-pending if the gate is on and no review state exists, and still writes its audit line. 4. pr_identity is ratcheted REAL but its real-gh tests never ran in CI. They move to tests/test_attach_pr_gh_402.py (no br needed) and the `test (real gh)` job runs it against the pinned fixture (#370, vars.PB_GH_FIXTURE_PR). The head check now precedes the fork check, so the refusal test doesn't depend on the fixture's origin. 5. A replacement PR kept the old PR's merged-verified stamp, letting the merge edge treat new code as gated; it is dropped with the other head pins. 6. A gh timeout / missing checkout on the replacement read escaped the tool and route; every attach gh read now converts WorktreeError/OSError into an actionable BoardError. The tools no-raise contract test now covers async tools (skipping them is how this slipped). 7. The loop parsed review_gate with bool() ("false" -> ON) while attach and the listings used knob_bool; the loop now uses knob_bool. Also: the audit comment is written last and cannot un-say a landed attach (a failure is reported as a warning); the docs say drafts attach (auto-merge holds them) and that attached code has not run the board's pre-PR checks (CI + the review gate still apply). The `_TaskStore`/`_BlockedStore` fakes in tests/test_loop.py now model the re-read the sweep does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2V6GRejF7mNukAoYjj2Av
mabry1985
added a commit
that referenced
this pull request
Sep 10, 2026
Adversarial review of #437 found seven defects; each has a test that fails on the previous head (8c45cd7) and passes here. 1. Ready-gate bypass: eligibility used the board STATE, and `blocked` is a flag on a lane, so a blocked backlog card (never Ready) or a blocked epic went straight into review. It is now the lane underneath the block (status without the flag) -- ready/in_progress, or in_review under a block -- and only an issue_type `feature`. 2. Re-attaching a blocked card's OWN PR lifted the block (e.g. "review findings persist -- needs human review") and re-armed the gate without a human. A card already tracking the PR is a no-op in review and refused anywhere else; the refusal names the block and that unblocking is a deliberate board_unblock_feature. 3. The health sweep moved cards on reads taken before a gh round-trip, outside the claim lock the attach writes under: the orphan pass and the blocked self-heal requeued an attach that had just landed. Both now re-read the card under the same lock and act only if it is still what they read (positive evidence of a move skips). When the sweep adopts the PR first, the attach reports already_attached, arms review-pending if the gate is on and no review state exists, and still writes its audit line. 4. pr_identity is ratcheted REAL but its real-gh tests never ran in CI. They move to tests/test_attach_pr_gh_402.py (no br needed) and the `test (real gh)` job runs it against the pinned fixture (#370, vars.PB_GH_FIXTURE_PR). The head check now precedes the fork check, so the refusal test doesn't depend on the fixture's origin. 5. A replacement PR kept the old PR's merged-verified stamp, letting the merge edge treat new code as gated; it is dropped with the other head pins. 6. A gh timeout / missing checkout on the replacement read escaped the tool and route; every attach gh read now converts WorktreeError/OSError into an actionable BoardError. The tools no-raise contract test now covers async tools (skipping them is how this slipped). 7. The loop parsed review_gate with bool() ("false" -> ON) while attach and the listings used knob_bool; the loop now uses knob_bool. Also: the audit comment is written last and cannot un-say a landed attach (a failure is reported as a warning); the docs say drafts attach (auto-merge holds them) and that attached code has not run the board's pre-PR checks (CI + the review gate still apply). The `_TaskStore`/`_BlockedStore` fakes in tests/test_loop.py now model the re-read the sweep does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2V6GRejF7mNukAoYjj2Av
mabry1985
added a commit
that referenced
this pull request
Sep 10, 2026
…#402) (#437) * feat(board): attach an externally opened PR to the card it belongs to (#402) The loop adopts a PR it did not see opened in exactly one place: crash recovery finds the PR whose head is the card's canonical branch and moves the card to in_review. A PR a human opened had no edge. On 2026-09-07 bd-ezs7's recovered implementation went up as PR #3369 on the card's own branch; the card sat terminal-blocked while the PR went through CI and review invisible to the board (re-dispatching would have force-removed the candidate worktree the work still lived in), and the PM hand-closed it with board_mark_done after the merge. board_attach_pr / POST /features/{fid}/attach-pr is that edge, no wider than recovery: - the PR must be the one the board would find for the card itself: OPEN, in the card's project repo and not a fork, head on the card's canonical branch (every later edge -- fix-round resume, recovery, the reap -- keys on it; a PR on another branch would be abandoned by the first fix round, which opens a second PR), base on the project's base; - the card must be an in-flight coding card (ready / in_progress / blocked) with no PR of its own in review and no open dependency; a card whose earlier PR is CLOSED may take the new one in its place (an open earlier PR would be orphaned, a merged one means it's done); - the loop must not be working it (live drive, claimed build, running review gate) -- refused, not overridden -- and the write runs under the loop's claim lock, so a ready card cannot be claimed halfway through. The write is one `br update` (store.attach_pr): in_review + external_ref, dropping ready/blocked/blocked-class and any stale review verdict or head pin, and arming review-pending when the review gate is on (else the merge edge waits forever for a verdict nothing produces). The ordinary reconcile then drives it: CI, review, rebase, merge -> done. An `attached PR:` comment records who, from what state, and why. Every refusal changes nothing and names what to do instead. New gh seam worktree.pr_identity (one `gh pr view`), classified REAL and read against the pinned PR in the GitHub tier; store.attach_pr classified REAL (every shape through real br). Also: the tools-doc guard matched only `def board_*`, so the async board_dispatch shipped undocumented and board_attach_pr would have too; it now matches async defs, and both are documented. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2V6GRejF7mNukAoYjj2Av * docs: changelog fragment for #437 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2V6GRejF7mNukAoYjj2Av * fix(board): the attach refusal tells the operator the exact branch to push to (#402) The wrong-branch refusal named the card's canonical branch but phrased the fix as "push the work there". It now leads with the instruction itself -- push the work to branch 'feat/<id>-<slug>', open the PR from it, and attach that PR -- and the test pins that instruction, not merely that the branch name appears somewhere in the message. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2V6GRejF7mNukAoYjj2Av * fix(board): review findings on the attach edge (#402) Adversarial review of #437 found seven defects; each has a test that fails on the previous head (8c45cd7) and passes here. 1. Ready-gate bypass: eligibility used the board STATE, and `blocked` is a flag on a lane, so a blocked backlog card (never Ready) or a blocked epic went straight into review. It is now the lane underneath the block (status without the flag) -- ready/in_progress, or in_review under a block -- and only an issue_type `feature`. 2. Re-attaching a blocked card's OWN PR lifted the block (e.g. "review findings persist -- needs human review") and re-armed the gate without a human. A card already tracking the PR is a no-op in review and refused anywhere else; the refusal names the block and that unblocking is a deliberate board_unblock_feature. 3. The health sweep moved cards on reads taken before a gh round-trip, outside the claim lock the attach writes under: the orphan pass and the blocked self-heal requeued an attach that had just landed. Both now re-read the card under the same lock and act only if it is still what they read (positive evidence of a move skips). When the sweep adopts the PR first, the attach reports already_attached, arms review-pending if the gate is on and no review state exists, and still writes its audit line. 4. pr_identity is ratcheted REAL but its real-gh tests never ran in CI. They move to tests/test_attach_pr_gh_402.py (no br needed) and the `test (real gh)` job runs it against the pinned fixture (#370, vars.PB_GH_FIXTURE_PR). The head check now precedes the fork check, so the refusal test doesn't depend on the fixture's origin. 5. A replacement PR kept the old PR's merged-verified stamp, letting the merge edge treat new code as gated; it is dropped with the other head pins. 6. A gh timeout / missing checkout on the replacement read escaped the tool and route; every attach gh read now converts WorktreeError/OSError into an actionable BoardError. The tools no-raise contract test now covers async tools (skipping them is how this slipped). 7. The loop parsed review_gate with bool() ("false" -> ON) while attach and the listings used knob_bool; the loop now uses knob_bool. Also: the audit comment is written last and cannot un-say a landed attach (a failure is reported as a warning); the docs say drafts attach (auto-merge holds them) and that attached code has not run the board's pre-PR checks (CI + the review gate still apply). The `_TaskStore`/`_BlockedStore` fakes in tests/test_loop.py now model the re-read the sweep does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2V6GRejF7mNukAoYjj2Av * fix(board): the attach edge takes the card's lock (#402) Rebased onto main, where #432 put the multi-call task edges (record_delivery, requeue, record_verification) under a per-card re-entrant lock: `_br_lock` single-flights each `br` CALL, not a read-then-write SEQUENCE. store.attach_pr is exactly such a sequence -- read the card, validate it, write the transition, write the audit line -- so it takes the same lock, and a concurrent requeue of the same card from another thread (a PM tool) can't land between its read and its write. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F2V6GRejF7mNukAoYjj2Av --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR is infrastructure. It exists to stay open.
tests/test_worktree_gh.py— therequires_ghtier from #361 S2 — exercises 12worktree.pyseams against real GitHub instead of a mock. Reading a real PR requires a real PR.CI resolves the fixture as:
On a
pull_requestrun the PR under test serves. On a push tomainthere is none, andPB_REQUIRE_GH=1deliberately turns "no fixture" into a build failure rather than a silent skip — the #136 lesson, because a tier that quietly skips is not coverage. The repo variablePB_GH_FIXTURE_PRpoints here somainkeeps its real-GitHub coverage.Why this matters
Those seams had zero real-API coverage until #361. That is how #354 shipped inert for a day:
POST /check-runsis GitHub-App-only and 403s under the board's PAT, but every unit test mockedgh— so 1,650 tests passed while the review verdict published to nothing, and a green PR sat there being rebuilt.Rules
OPEN; closing turnsmainredStatus writes go to the commit under test, never to this head — GitHub caps statuses at 1,000 per
(sha, context), so writing to a stable head would be a slow time bomb (found in review on #361 S2).If it is ever lost: open any permanently-open PR and repoint
vars.PB_GH_FIXTURE_PR.