Skip to content

fix(github-webhook): route PR-review author wakes to the owning issue, not an arbitrary Related: backlink (BLO-20886) - #1367

Merged
allyblockcast[bot] merged 1 commit into
masterfrom
cto/blo-20886-linear-successor
Aug 15, 2026
Merged

fix(github-webhook): route PR-review author wakes to the owning issue, not an arbitrary Related: backlink (BLO-20886)#1367
allyblockcast[bot] merged 1 commit into
masterfrom
cto/blo-20886-linear-successor

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
  • Agents are woken by control-plane webhooks; github_pr_review_requested is the wake that tells an agent a reviewer has responded on a PR it owns
  • That wake looped over every Paperclip identifier appearing anywhere in a PR body, so a PR listing Related: BLO-20810, BLO-20129, BLO-19079 woke all of them, not just its owner
  • Each of those agents received an author-directed directive asserting "a reviewer posted findings on YOUR pull request … push a follow-up commit" — a claim the webhook never verified
  • Both halves could be false at once. On [codex] add idempotent approval create and cheap listing (BLO-19132) #953 the PR was authored by a human, had zero reviews at wake time, and the trigger was the human requesting review. An agent following that directive would have pushed into a contributor's branch on the strength of findings that did not exist — and [codex] add idempotent approval create and cheap listing (BLO-19132) #953 existed precisely because it needed a non-bot author
  • This pull request restricts the author-wake loop to the PR's owning issue via resolveOwningPaperclipIdentifiers (branch > title > labeled Fixes:/Closes:/Refs:), failing closed to a logged no_owning_reference suppression rather than falling through to a Related: mention's assignee
  • The benefit is that an author-directed "push a commit" directive can only ever reach the agent that actually owns the PR, and reaches nobody when ownership is unprovable

Linked Issues or Issue Description

Duplicate search: reviewed the open PR list for overlapping webhook-routing work. #962 (superseded by this PR) and #997 (adjacent, directive text) are the only related PRs; no other open PR touches resolveOwningPaperclipIdentifiers.

What Changed

  • paperclip-identifiers.ts — adds resolveOwningPaperclipIdentifiers with an explicit precedence: title > labeled Fixes:/Closes:/Refs: body line > branch name (branch demoted to a case-insensitive last resort). Bounded parsing; CommonMark-correct fence handling so an identifier named only inside a fenced example or a bulleted code block cannot be read as an owning reference.
  • github-webhook.ts — the author-wake loop now iterates owning identifiers only, not every matched identifier. When nothing resolves, it emits a logged no_owning_reference suppression instead of falling through.
  • issue-pull-requests.ts — ownership selection aligned with the same resolver.
  • heartbeat.ts — heartbeat context reflects the resolved owning reference.
  • ReviewQueueCard.tsx test — empty review queue polls on one timer rather than two.
  • Tests — issue-pull-requests-ownership-selection.test.ts (new), plus coverage in github-webhook.test.ts and heartbeat-context-summary.test.ts.

Verification

Run locally on this branch:

npx vitest run server/src/__tests__/github-webhook.test.ts \
  server/src/__tests__/issue-pull-requests-ownership-selection.test.ts \
  server/src/__tests__/heartbeat-context-summary.test.ts   # 265 passed
npx vitest run ui/src/pages/apps/ReviewQueueCard.test.tsx  # 8 passed
pnpm --filter @paperclipai/server typecheck                # exit 0
node scripts/check-commit-author-attribution.mjs --base origin/master --head HEAD  # pass

Acceptance-criteria tests, by name:

  • resolves the PR's OWNING identifier as title > labeled Fixes:/Closes:/Refs: body line > branch, never a bare Related: mention (BLO-20886)
  • routes an author wake to the PR's owning Refs: issue, never an unrelated Related: backlink assignee (BLO-20886)
  • suppresses the author wake with a logged reason when a PR carries only Related: mentions and no owning reference (BLO-20886)
  • routes an issue_comment @ally-review author wake to a PR's owning issue named only by a house-reference label, not a Related: mention (BLO-21312)

Equivalence to the reviewed content of #962:

Check Result
Per-file content vs #962 head 94c6156e9 identical, all 8 files
master..HEAD patch vs #962's contribution identical diff
Merge commits in range 0 (linear)

Risks

Low risk to runtime behavior; the change is fail-closed. When ownership cannot be resolved the wake is suppressed and logged, never redirected — so the failure mode is a missing wake (visible in logs) rather than a wrong agent being told to push. The blast radius of the old behavior was the opposite: silent, and pointed at someone else's branch.

This PR is a linearized successor to #962, not new work. #962 could not merge: the merge queue is mergeMethod: REBASE / ALLGREEN, and its branch was 20 commits with 7 interleaved master merges, which a rebase queue cannot replay. It was ejected four times, ~15s each, reporting nothing — while every status surface read green. CI on 94c6156e9 was 19 success / 1 skipped / 0 failures; the shape was the blocker, never the content.

Attribution. A per-commit rebase was attempted first and is not available: the early commits were written against a master that has since moved substantially under these same files, and only the dropped merges reconcile that drift. The collapse is therefore forced by the branch's shape. All 13 original commits are enumerated by SHA in the commit message, and every contributor is carried as a Co-Authored-By: trailer (Omar Ramadan, PlatformSREEngineer, allyblockcast[bot], Claude / Claude Sonnet 5).

Recovery. sre/blo-20886-pr-review-wake-routing is left intact at 94c6156e9328556729be78a7afe65215838c4c20. Nothing was force-pushed; #962 remains readable as the full review history.

One latent issue resolved rather than inherited. #962 carried three allyblockcast[bot]-authored non-merge commits; two were SHA-pinned in GRANDFATHERED_OFFENSE_SHAS, but 6e7440da2 was not, and passed check-commit-author-attribution.mjs only via the BLO-26647 defect. This branch has zero App-authored non-merge commits, so the gate passes structurally with no allowlist entries added.

Model Used

Claude Opus 4.5 (claude-opus-4-5), extended thinking, agentic tool use via Claude Code (file editing, shell, gh CLI, Paperclip MCP). Original commits were produced across multiple sessions — Claude Sonnet 5 and Claude are credited in the collapsed commit's Co-Authored-By: trailers.

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, the only UI-path change is a polling-timer fix with no visual delta
  • I have updated relevant documentation to reflect my changes — n/a, no user-facing docs affected
  • I have considered and documented any risks above
  • All Paperclip CI gates are green — in progress; policy, security-review, Helm chart, and the vendored-adapter lane are green, review re-running against this description
  • Greptile is 5/5 with no open P2s, recommendations, or follow-ups — not yet run against this head
  • I will address all Greptile and reviewer comments before requesting merge

@allyblockcast

allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-20886
🔗 Paperclip issue: BLO-26647
🔗 Paperclip issue: BLO-21312

1 similar comment
@allyblockcast

allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-20886
🔗 Paperclip issue: BLO-26647
🔗 Paperclip issue: BLO-21312

@allyblockcast

allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown
Author

Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention:

Missing or incomplete:

  • Missing section: ## Thinking Path
  • Missing section: ## What Changed
  • Missing section: ## Risks
  • Missing section: ## Model Used
  • Add the dedup-search checkbox to your PR description and check it once you have searched the GitHub PR list for similar PRs. See the PR template at .github/PULL_REQUEST_TEMPLATE.md and CONTRIBUTING.md → "Before You Start: Search First".

Once updated, push a new commit and these checks will re-run automatically.

— commitperclip

@allyblockcast allyblockcast Bot closed this Aug 15, 2026
@allyblockcast allyblockcast Bot reopened this Aug 15, 2026
@allyblockcast

allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown
Author

CI failures here are ARC runner evictions, not a code defect — re-run triggered

Drive-by from the CTO lane. I was woken against this PR (github_pr_reopened) and checked its gate state before doing anything else. Posting what I found so nobody spends a run debugging a phantom typecheck bug.

All 6 primary failures at head 240a5bc6 are infrastructure evictions. Every one carries ##[error]The runner has received a shutdown signal. and zero TypeScript diagnostics (grep -cE "TS[0-9]{4}" = 0 on all of them):

check evicted TS errors evicted at
Typecheck + Release Registry 0 05:37:41.8Z
Canary Dry Run 0 05:37:41.7Z
Worktree install (NODE_ENV=production) 0 05:37:41.7Z
General tests (server 1/4) 0 05:37:42.7Z
General tests (server 2/4) 0 05:37:42.9Z
k8s-ro seed transport cold start 0 05:37:42.7Z

All six died inside ~1.3 seconds of each other, which is a node-level event rather than six independent failures. verify is not a 7th failure — it is the aggregator, and its log says so verbatim: Upstream lane(s) reported failure: typecheck_release_registry general_tests worktree_install opencode_k8s_seed_cold_start.

Everything that was allowed to finish passed: Build, e2e, policy, security-review, review, Helm chart, General tests (server 3/4, 4/4, workspaces-a, workspaces-b), OpenCode Responses replay, Vendored claude_k8s adapter.

review is green, so the commitperclip comment above (missing ## Thinking Path / ## What Changed / ## Risks / ## Model Used / dedup checkbox) is stale — it ran at 05:27:47Z against the pre-edit body and the description has since satisfied the template. No action needed there.

Action taken: re-ran the failed jobs (gh run rerun 31867145521 --failed) — now attempt 2, in progress. I checked arc-runners first: pods are scheduling normally and there are no Evicted events outstanding, so the 05:37 wave looks transient and the re-run should stick. If it evicts again, that is the known image-fs eviction pressure (BLO-20369), not this branch.

Not reviewed by me — I have not read the diff and this is not an approval. Only the CI diagnosis above.


Aside: this wake is itself an instance of the bug this PR fixes

I own BLO-20810. This PR does not reference BLO-20810 in its backlinks (those are BLO-20886 / BLO-26647 / BLO-21312) — BLO-20810 appears only as prose inside this PR's own Thinking Path, in the sentence describing the defect:

a PR listing Related: BLO-20810, BLO-20129, BLO-19079 woke all of them, not just its owner

The unfixed webhook matched that body mention and woke me, the owner of an unrelated issue, with the author-directed directive "a reviewer just posted findings on YOUR pull request … push a follow-up commit." Both halves were false: this PR is not mine, and there are zero reviews on it — I checked both surfaces (pulls/1367/reviews is empty; no ## Ally comment body exists). The triggering event was a close+reopen 6s apart, not a review.

So the PR's own worked example reproduced itself, out of its own description, onto its own author-wake path. That is a fairly direct argument for resolveOwningPaperclipIdentifiers failing closed to no_owning_reference. Cross-posting to BLO-20886.

@allyblockcast

allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown
Author

Correction to my comment above — this PR is mine; the routing finding stands, my framing of it did not

Self-reporting an error in my previous comment before it misleads anyone.

I wrote "this PR is not mine" and described myself as "drive-by from the CTO lane." That is wrong. I checked afterwards: BLO-20886 — this PR's owning issue — is assigned to me (CTO). So I own #1367. The wake directive telling me this PR's status was my responsibility was, in that respect, correct, and I mischaracterised it.

What actually survives, and it is the part that mattered: the wake still routed through the wrong identifier. It fired on BLO-20810, which appears in this body exactly once — inside inline-code backticks, inside a bullet, inside a blockquote, as a hypothetical example — and not via Refs: BLO-20886, the real owning reference. That is still the fan-out defect this PR fixes, and the buried-in-a-blockquote detail is still the sharpest instance on file.

What changes is the severity of this particular instance: because I happen to own both issues, the mis-route was benign here — it landed on the right agent by luck, not by correctness. It is evidence of the routing bug, not evidence of the damage path. The #953 case remains the one that demonstrates harm, where the fan-out reached an agent who genuinely did not own the branch. I should not have implied this instance showed the same.

Unchanged and still accurate:

  • All 6 CI failures at 240a5bc6 are ARC runner evictions (runner has received a shutdown signal, zero TS diagnostics, all inside ~1.3s at 05:37:41–42Z), not a code defect.
  • verify is the aggregator, not a 7th failure.
  • The commitperclip template complaint is stale — review is green.
  • Re-run is attempt 2, in progress; arc-runners was scheduling normally when I triggered it.

Also unchanged: there are still zero reviews on this PR (both surfaces empty), so there are no findings to address and I have pushed no code. Correcting the record on BLO-20886 too.

@allyblockcast

allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown
Author

Confirmed — re-run is green on the same commit, zero code changed

Closing the loop on the eviction diagnosis. Attempt 2 on the identical head 240a5bc6, no push, no rebase:

check attempt 1 attempt 2
Typecheck + Release Registry ❌ failure success
Worktree install (NODE_ENV=production) ❌ failure success
k8s-ro seed transport cold start ❌ failure success
General tests (server 3/4, 4/4, workspaces-a/b)
Build · Helm chart · e2e · OpenCode Responses replay · Vendored claude_k8s adapter

Same commit, same code, opposite result — which is only possible if attempt 1's failures were environmental. That settles it: they were ARC runner evictions, and there was never a defect in this branch to chase. Canary Dry Run and General tests (server 1/4, 2/4) are still running.

mergeStateStatus is now BEHIND. Flagging one thing for whoever picks this up next, myself included: BEHIND takes precedence over BLOCKED in GitHub's status ordering, so it masks any other gate. Until the branch is updated, "blocked by X" is an untested inference about this PR — the only thing currently known is that it is behind master. I am not updating the branch while attempt 2 is still in flight, since that would move the head and restart it.

Still zero reviews on both surfaces, so nothing is outstanding against the diff.

@allyblockcast
allyblockcast Bot added this pull request to the merge queue Aug 15, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 15, 2026
kkroo pushed a commit that referenced this pull request Aug 15, 2026
…leaks (BLO-23893)

Re-linearizes PR #1367 onto current master after the merge queue ejected it
at 2026-08-15T11:32:36Z: the branch was linear (1 commit, 1 parent) but had
gone `dirty` against master while queued.

Closes four owning-identifier parser findings from BLO-20886 / Ally's review
at 68726df:
1. lowercase branch-only owners dropped before routing
2. a list-prefixed pseudo-closer reopening a fence
3. mixed space-tab indentation not treated as indented code
4. house-reference labels scanning the raw body

plus a fifth the finding-1 fix would otherwise have armed: the branch tier
now anchors to a path-segment boundary.

Conflict resolution (server/src/routes/github-webhook.ts, issue_comment
tier): master landed BLO-23267, which drops `commentBody` from the broad
`identifiers` set so review prose can no longer attribute a wake. This
branch adds `owningIdentifiers`. The two are complementary, so both are
kept: identifiers stay title/body-only per BLO-23267, and the owning tier
is added alongside.

Refs: BLO-23893
@kkroo
kkroo force-pushed the cto/blo-20886-linear-successor branch from 240a5bc to c3f0e40 Compare August 15, 2026 18:00
…leaks (BLO-23893)

Re-linearizes PR #1367 onto current master after the merge queue ejected it
at 2026-08-15T11:32:36Z: the branch was linear (1 commit, 1 parent) but had
gone `dirty` against master while queued.

Closes four owning-identifier parser findings from BLO-20886 / Ally's review
at 68726df:
1. lowercase branch-only owners dropped before routing
2. a list-prefixed pseudo-closer reopening a fence
3. mixed space-tab indentation not treated as indented code
4. house-reference labels scanning the raw body

plus a fifth the finding-1 fix would otherwise have armed: the branch tier
now anchors to a path-segment boundary.

Conflict resolution (issue_comment tier): master landed BLO-23267, which
drops `commentBody` from the broad `identifiers` set so review prose can no
longer attribute a wake. This branch adds `owningIdentifiers`. The two are
complementary, so both are kept: identifiers stay title/body-only per
BLO-23267, and the owning tier is added alongside.

Also reports `reviewerWakeFired` on the `no_matching_issue` and final
success responses. It is computed for every delivery but was previously
emitted only on the two `no_paperclip_identifier` exits. That gap was
invisible until finding 1's fix landed: the BLO-21995 fixtures are PRs whose
only reference is a lowercase branch, so they no longer exit at that gate,
reach a later response, and the field silently vanished -- failing 4 tests
whose real subject (a response must never claim a wake it did not fire) is
unchanged. The wake's outcome is a property of the delivery, not of which
exit it happens to take.

Refs: BLO-23893
@kkroo
kkroo force-pushed the cto/blo-20886-linear-successor branch from c3f0e40 to 8a76a4c Compare August 15, 2026 18:32
@allyblockcast
allyblockcast Bot added this pull request to the merge queue Aug 15, 2026
Merged via the queue into master with commit 08bed22 Aug 15, 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