fix(heartbeat): stop claiming "YOUR pull request" on a PR a third party wrote (BLO-20886 AC3) - #1379
Merged
Merged
Conversation
…ty wrote (BLO-20886) BLO-20886 AC3, the last open code AC on that issue. Owning-issue routing (#1367, merged 08-15) fixed *which* issue receives the author-directed PR wake, but it does not make the recipient the PR's author, and the directive still asserted that it did. The gap is not hypothetical, and it is the issue's original damage path. A human branch named `kkroo/blo-19132-*` resolves to BLO-19132 through the resolver's branch tier, so BLO-19132's assignee is woken and told "a reviewer just posted findings on YOUR pull request ... push a follow-up commit" about a branch a human owns. paperclip#953 was exactly this, and pushing there is actively harmful: #953 existed precisely to have a non-`allyblockcast[bot]` author, so a bot commit destroys the independence the PR was opened to establish. 3 of the 100 most recent paperclip PRs are `kkroo/blo-*`, so the shape recurs. Every agent authors through the same GitHub App installation, so we cannot tell WHICH agent wrote a bot-authored PR -- but we can tell for certain when NO agent did. `pull_request.user.login` is already carried on the wake context (BLO-9293, for the reviewer self-review-skip gate); compare it to the configured `prReviewerBotLogin` and, on a mismatch, drop both the possessive and the push instruction, naming the real author instead. Fails OPEN when the author login is absent: an unknown author is not proof of third-party authorship and the bot-authored case is the common one, so the gate fires only on a positive, signed-webhook mismatch. Verified end to end -- githubContextMetadata -> contextSnapshot -> derivePaperclipPrReview -> buildPaperclipTaskMarkdown -- rather than only in the unit under test. Tests: 93/93 in heartbeat-context-summary, including a third-party case asserting the render contains neither "YOUR pull request" nor "push a follow-up commit", a bot-authored control proving the possessive is not blanket-stripped, an unknown-author fail-open case, and the review_requested variant. Typecheck: 59 errors before and after, identical modulo the +60-line shift, so zero introduced (the 59 are stale borrowed workspace dists in this worktree). Refs: BLO-20886 Co-Authored-By: Claude <noreply@anthropic.com>
Author
1 similar comment
Author
This was referenced Aug 16, 2026
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.
Thinking Path
Linked Issues or Issue Description
Refs: BLO-20886— the owning issue (AC3: the directive must stop asserting facts the webhook has not checked)Related, already merged, and not superseded by this PR:
no_owning_referencesuppression, and the review-content wakeReason allowlistWhat Changed
resolveThirdPartyPrAuthor()inserver/src/services/heartbeat.ts— compares the wake'sprAuthorLoginagainst the configuredprReviewerBotLoginand returns the author's login when they differ,nullotherwise.YOUR pull requestand thepush a follow-up commitinstruction for a third-party-authored PR, names the real author, and says to comment rather than commit. Findings are still delivered in full — the review is real, only the ownership claim was false.prAuthorLoginadded tobuildPaperclipTaskMarkdown'sprReviewinput type. It was already produced byderivePaperclipPrReviewand already set bygithubContextMetadata, so no new plumbing was needed — only the type and the comment ingithub-webhook.tsexplaining the second consumer.Deliberately fails open: an absent author login is not proof of third-party authorship, and bot-authored is the common case, so the gate fires only on a positive, signed-webhook mismatch.
Verification
Four new tests, covering both directions so the gate cannot pass by being a no-op:
kkroo) +review_submittedYOUR pull requestnorpush a follow-up commit; names the author; still renders the review bodyallyblockcast[bot]authorednull)review_requestedWiring verified end to end rather than only in the unit under test:
githubContextMetadata(github-webhook.ts:3240) →contextSnapshot.githubPrAuthorLogin→derivePaperclipPrReview→buildPaperclipTaskMarkdown(heartbeat.ts:22348, which passes the derived object straight through).Typecheck: 59 errors before and after, identical modulo a +60-line shift on three pre-existing ones — zero introduced. (Those 59 come from stale borrowed workspace dists in the local worktree, not from this change; CI builds fresh.)
Blast radius measured, not assumed: 3 of the 100 most recent PRs on this repo are non-bot-authored with an owning-tier
BLO-ref (#1287/#1288/#1289, allkkroo/blo-21953-*), plus #953 itself.Risks
Low, and one-directional. The only behavioral change is removing two claims from a directive when the webhook proves them false; no routing, suppression, or wake-count behavior is touched.
PAPERCLIP_PR_REVIEWER_BOT_LOGINwere ever misconfigured to a non-bot value, bot-authored PRs would render the third-party wording — the agent would be told not to commit to its own branch. Conservative direction (a stalled PR, not a bad push), andindex.ts:272already validates the value is an App identity at boot.Model Used
claude-opus-4-5, 1M context), extended thinking, with tool use.Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template