Skip to content

fix(heartbeat): stop claiming "YOUR pull request" on a PR a third party wrote (BLO-20886 AC3) - #1379

Merged
allyblockcast[bot] merged 1 commit into
masterfrom
cto/blo-20886-ac3-author-gate
Aug 16, 2026
Merged

fix(heartbeat): stop claiming "YOUR pull request" on a PR a third party wrote (BLO-20886 AC3)#1379
allyblockcast[bot] merged 1 commit into
masterfrom
cto/blo-20886-ac3-author-gate

Conversation

@allyblockcast

@allyblockcast allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown

Thinking Path

  • Paperclip is the open source app people use to manage AI agents for work
  • The GitHub webhook router turns PR events into agent wakes, and renders a directive telling the woken agent what to do about the PR
  • BLO-20886 found that directive asserting two unchecked facts — that the PR is yours, and that a reviewer left findings on it — and instructing a push on that basis
  • fix(github-webhook): route PR-review author wakes to the owning issue, not an arbitrary Related: backlink (BLO-20886) #1367 (merged 08-15) fixed the routing half: the author wake now goes only to the PR's owning issue, not to every Related: backlink
  • But owning-issue routing picks the right ISSUE, not the right PERSON. A human branch kkroo/blo-19132-* resolves to BLO-19132 via the resolver's branch tier, so that issue's assignee is still told "YOUR pull request … push a follow-up commit" about a branch a human owns
  • This pull request closes BLO-20886 AC3 by gating the possessive and the push instruction on the signed webhook's pull_request.user.login
  • The benefit is that the one remaining path to the original paperclip#953 damage — a bot commit landing on a human's branch — is closed

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:

What Changed

  • resolveThirdPartyPrAuthor() in server/src/services/heartbeat.ts — compares the wake's prAuthorLogin against the configured prReviewerBotLogin and returns the author's login when they differ, null otherwise.
  • The review-feedback author branch now drops YOUR pull request and the push a follow-up commit instruction 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.
  • The review-request author branch likewise drops the possessive and names the author.
  • prAuthorLogin added to buildPaperclipTaskMarkdown's prReview input type. It was already produced by derivePaperclipPrReview and already set by githubContextMetadata, so no new plumbing was needed — only the type and the comment in github-webhook.ts explaining 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

npx vitest run src/__tests__/heartbeat-context-summary.test.ts   # 93/93 passed

Four new tests, covering both directions so the gate cannot pass by being a no-op:

test asserts
third-party authored (kkroo) + review_submitted contains neither YOUR pull request nor push a follow-up commit; names the author; still renders the review body
controlallyblockcast[bot] authored possessive and push instruction both retained (proves the gate is not blanket-stripping)
unknown author (null) fails open, possessive retained
third-party + review_requested possessive dropped on the request branch too

Wiring verified end to end rather than only in the unit under test: githubContextMetadata (github-webhook.ts:3240) → contextSnapshot.githubPrAuthorLoginderivePaperclipPrReviewbuildPaperclipTaskMarkdown (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, all kkroo/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.

  • If PAPERCLIP_PR_REVIEWER_BOT_LOGIN were 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), and index.ts:272 already validates the value is an App identity at boot.
  • The gate cannot distinguish which agent authored a bot-authored PR, so agent-A-woken-about-agent-B's-PR keeps the possessive. That is a real residual, and it is not closable from webhook data alone — every agent shares one App installation identity. It is also far less dangerous than the case fixed here: both are agent branches, so a push is recoverable and does not destroy a human-authorship guarantee.

Model Used

  • Claude Opus 4.5 (claude-opus-4-5, 1M context), extended thinking, with tool use.

Checklist

  • I have included a thinking path that traces from project context to this change
  • I have specified the model used (with version and capability details)
  • I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
  • I have searched GitHub for duplicate or related PRs and linked them above
  • I have either (a) linked existing issues with Fixes: # / Closes # / Refs # OR (b) described the issue in-PR following the relevant issue template
  • I have run tests locally and they pass
  • I have added or updated tests where applicable
  • If this change affects the UI, I have included before/after screenshots — n/a, no UI surface
  • I have updated relevant documentation to reflect my changes — the rule is documented in code comments at both gate sites
  • I have considered and documented any risks above
  • All Paperclip CI gates are green — pending first run
  • Greptile is 5/5 with no open P2s, recommendations, or follow-ups
  • I will address all Greptile and reviewer comments before requesting merge

…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>
@allyblockcast

allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-20886
🔗 Paperclip issue: BLO-19132

1 similar comment
@allyblockcast

allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-20886
🔗 Paperclip issue: BLO-19132

@allyblockcast
allyblockcast Bot enabled auto-merge August 15, 2026 21:52
@allyblockcast
allyblockcast Bot added this pull request to the merge queue Aug 15, 2026
Merged via the queue into master with commit 0b2bc0d Aug 16, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants