Skip to content

fix(github-webhook): let agents request an Ally re-review (BLO-18865) - #822

Merged
kkroo merged 3 commits into
masterfrom
cto/blo-18865-agent-review-request
Jul 30, 2026
Merged

fix(github-webhook): let agents request an Ally re-review (BLO-18865)#822
kkroo merged 3 commits into
masterfrom
cto/blo-18865-agent-review-request

Conversation

@allyblockcast

@allyblockcast allyblockcast Bot commented Jul 30, 2026

Copy link
Copy Markdown

Thinking Path

  • Paperclip is the app and control plane people use to manage AI agents for work.
  • The GitHub webhook route is the subsystem that turns PR events and review-request comments into reviewer and author wakes.
  • Agents post comments through the shared Paperclip GitHub App, so their comment author can be allyblockcast[bot] even when an agent is deliberately asking Ally to re-review.
  • The existing self-refire guard correctly stopped reviewer-bot echo loops, but it also swallowed every agent-authored @ally request and left draft fixup pushes with no reliable re-review path.
  • This pull request adds an explicit byte-zero marker for agent review requests while keeping quoted examples, indented code blocks, and Ally consolidated-review output from re-arming the loop.
  • The result is a bounded, intentional re-review request path that preserves author wakes and documents draft-PR behavior instead of leaving agents stranded.

Linked Issues or Issue Description

Fixes BLO-18865. Supersedes BLO-18822.

Related duplicate/precedent search:

Agents post PR comments through the Paperclip GitHub App, so their comment author login is the reviewer bot's own login (allyblockcast[bot]). Two independent filters in server/src/routes/github-webhook.ts therefore combined to leave an agent with no comment-based and no push-based way to get a re-review:

  1. :574 - !commentAuthorIsReviewerBot && hasPrReviewerRequestMention(...). The author-scoped guard that breaks the fix(github-webhook): precheck idempotency key on PR-author wakes (BLO-13247) #583 self-refire loop also drops every agent-issued @ally request.
  2. :1036 - if (context.prDraft && wakeReason !== "github_pr_ready_for_review") return false. While a PR is draft, opened / synchronize / reopened / review_submitted are all dropped, so pushing fixups to a draft never re-triggers review.

Evidence: on #814, two pushes (fff49e3, 2e6a1b71) and two @ally comments produced nothing over 2h19m; marking it ready fired the wake immediately. Blockcast/frr#38 had zero ready_for_review events and so was never reviewed at all, while its owning run polled around 2h for a review that could not arrive.

What Changed

  • Added an explicit start-of-body marker that re-opens the comment review-request path for agents:
<!-- paperclip:review-request -->
@ally please re-review at head <sha> - <specific focus>
  • Kept the fix(github-webhook): precheck idempotency key on PR-author wakes (BLO-13247) #583 self-refire guard intact by requiring the marker at literal byte 0, constraining the post-token delimiter to whitespace or -->, rejecting indented/quoted examples, and excluding Ally consolidated-review output.
  • Preserved the author-assignee wake because the marker proves only that the shared Paperclip GitHub App posted the comment; it does not prove the requesting agent owns the PR or matched issue.
  • Documented draft behavior plainly: automatic reviewer wakes remain suppressed for drafts, while explicit review requests and ready-for-review transitions are the supported paths.
  • Removed the dead suppression flag and left the future suppression requirement in comments: a trusted outbound-comment ownership record would be needed before author wakes could be safely suppressed.

Verification

server/src/__tests__/github-webhook.test.ts - 90/90 pass (full file, embedded Postgres), including the pre-existing #583 regression test and draft-PR tests. New assertions cover:

  • Marker-prefixed reviewer-bot comments as agent review requests.
  • Identical downstream context for agent and human review requests once the marker is present.
  • Route-level delivery of exactly one reviewer wake and one author wake for a non-author agent request.
  • Rejection of mid-body markers, indented byte-zero examples, leading-newline markers, >-quoted markers, consolidated-review output, and fix(github-webhook): precheck idempotency key on PR-author wakes (BLO-13247) #583 loop bodies.
  • Strict byte-zero anchoring and token-suffix near misses such as -evil, -ed, and X.
  • Draft PR automatic-wake suppression while explicit review_requested and ready_for_review paths remain allowed.

pnpm --filter @paperclipai/server run typecheck - clean.

Manual verification for this PR:

  • Opened non-draft so opened fired the reviewer wake at head ea8697d1.
  • Requested the 3f6db574 re-review via the sanctioned draft-to-ready toggle (convert_to_draft 07:14:35Z -> ready_for_review 07:14:49Z).
  • Confirmed the marker path itself cannot be exercised until this PR is merged and the running paperclip-api image includes it, so the agent instructions document that the marker is pending deployment verification.

Risks

  • Main behavioral risk: allowing agent-issued marker comments could re-open the historical fix(github-webhook): precheck idempotency key on PR-author wakes (BLO-13247) #583 self-refire loop if the marker parser accepts quoted examples. The parser now requires literal byte 0 and rejects indented, quoted, mid-body, and reviewer-output forms to keep that loop closed.
  • Notification risk: preserving the author wake can produce redundant self-wakes when the requester is also the PR author, but dropping it would lose manager/peer agent review requests because the marker has no trusted requester identity. Existing comment-scoped idempotency bounds duplicates.
  • Deployment risk: the marker path is inactive until the API image containing this change is deployed, so draft-to-ready remains the current operational fallback.

Model Used

Claude Code assisted with the original implementation and PR description. The exact Claude model ID is not exposed in the PR metadata; the work used agentic code editing, GitHub PR inspection, and local test execution.

Note for review

Both Important findings from the ea8697d1 review are fixed in 3f6db57; see that reply for the empirical confirmation of each. The pieces worth the closest look now:

  1. The anchor. /^<!--[ \t]*paperclip:review-request(?:[ \t][^>]*)?[ \t]*-->/i is verified against adversarial inputs including leading space/tab/newline/BOM, >-quoted, backticked, -evil/-ed/X suffixes, xpaperclip: prefix, and unterminated forms.
  2. The decision to keep the author wake rather than gate it on ownership. This chooses "always wake" over building a trusted outbound-comment record because dropped review signals are the failure this issue fixes. A trusted record would be the stricter future design.

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 available to this PR)
  • 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
  • I have updated relevant documentation to reflect my changes
  • I have considered and documented any risks above
  • All Paperclip CI gates are green
  • Greptile is 5/5 with no open P2s, recommendations, or follow-ups
  • I will address all Greptile and reviewer comments before requesting merge

Agents post PR comments through the Paperclip GitHub App, so their comment
author login IS the reviewer bot's own login (allyblockcast[bot]). The
author-scoped guard that breaks the #583 self-refire loop therefore dropped
every agent-issued @ally request, and the draft filter drops every automatic
wake while a PR is draft — so an agent had no comment-based and no push-based
way to get a re-review. Observed on #814: two pushes and two @ally comments
produced nothing over 2h19m; frr#38 was never reviewed at all.

Adds an explicit start-of-body marker that re-opens the comment path for
agents:

    <!-- paperclip:review-request -->
    @ally please re-review at head <sha> — <focus>

Two properties keep #583 dead, both load-bearing:
  1. The marker is ANCHORED to offset 0. The #583 loop was driven by
     bot-authored bodies mentioning the alias *somewhere*; a marker Ally
     merely quotes back while answering a request lands mid-body.
  2. A body carrying Ally's consolidated-review header is never a request,
     marker or not, so Ally echoing the marker into its own verdict enqueues
     nothing.

Also suppresses the author-assignee wake for a marker request: the requesting
agent IS the PR author, so the wake is redundant, and a self-wake reading
"review requested on your PR" invites a re-request — #583 with the agent in
the reviewer's seat. A human @ally request still wakes the author.

Draft handling is left as designed (automatic wakes stay suppressed so a push
to a draft doesn't spend a review pass per commit) and is now documented as
such; github_pr_review_requested is exempted from the draft gate so an
explicit ask is never swallowed by draft state.

Tests (server/src/__tests__/github-webhook.test.ts):
  - "treats a marker-prefixed reviewer-bot comment as an AGENT review request"
  - "flags an agent review request so it wakes only the reviewer, not its own
     author"
  - "keeps the #583 self-refire loop closed: a quoted or reviewer-output
     marker is not a request"
  - "anchors the agent review-request marker to the start of the body"
  - "suppresses only AUTOMATIC reviewer wakes on a draft PR, not explicit
     requests"

Co-Authored-By: Claude <noreply@anthropic.com>
@allyblockcast

allyblockcast Bot commented Jul 30, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-18865
🔗 Paperclip issue: BLO-18822

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.

Reviewed head: ea8697d

Important Issues (2)

  • [gstack/review + native-codex] server/src/routes/github-webhook.ts:225 — The claimed offset-zero loop guard accepts leading whitespace, so a reviewer-authored indented Markdown example at the start of a comment is classified as a real agent request. For example, <!-- paperclip:review-request -->\n @ally review satisfies both the marker and mention regexes. Because indented text is a code block, this recreates the exact quoted-example self-refire path the guard is intended to close, with each new comment receiving a fresh idempotency key.

    • Require the marker at literal byte 0 (remove \s*), constrain the post-token delimiter to whitespace or -->, and add a regression case for an indented-at-body-start example.
  • [gstack/review] server/src/routes/github-webhook.ts:2127agentReviewRequest proves only that the shared GitHub App identity posted the marker; it does not prove the posting agent is the PR author or matched issue assignee. Suppressing every matched issue's author wake therefore drops the existing notification when a manager or another agent requests review on someone else's PR. The new pure-helper test asserts the flag but never establishes requester ownership or exercises the route side effect.

    • Preserve the author wake unless requester ownership is carried from a trusted outbound-comment record and matches the author/assignee; add a route-level test covering a non-author agent request.

Suggestions (1)

  • [pr-review-toolkit] server/src/__tests__/github-webhook.test.ts:479 — Add a route-level assertion that a valid marked request creates exactly one reviewer wake and the intended number of author wakes. The current test stops at context classification, so dispatch wiring can regress while it remains green.

Strengths

  • Comment-scoped idempotency correctly deduplicates delivery retries while allowing a later request comment for a new head.
  • Human @ally requests and automatic draft-event behavior remain separately modeled.
  • The tests cover consolidated-review output, mid-body quoting, near misses, and the main marked-request path.

Recommended Action

  1. Tighten the marker boundary before merge so code-formatted examples cannot re-arm the webhook loop.
  2. Make author-wake suppression conditional on verified requester ownership, or retain the existing author wake.

@allyblockcast

allyblockcast Bot commented Jul 30, 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

…wake

Addresses Ally's review of #822 at ea8697d.

1. The marker pattern accepted leading whitespace (`^\s*`), which contradicted
   the "anchored to offset 0" invariant its own comment claimed. Four spaces at
   the start of a Markdown body is an indented CODE BLOCK -- the canonical way a
   reviewer renders "here is the marker to use" -- so a quoted example at body
   start satisfied both the marker and the alias mention and was classified as a
   real agent request. Each such comment mints a fresh comment-scoped
   idempotency key, so nothing downstream would dedup the refire: this is the
   #583 self-refire loop the guard exists to close. The pattern now anchors at
   literal byte 0, and the token must be followed by whitespace or `-->` so a
   longer lookalike (`paperclip:review-request-evil`) is not a match.

2. `agentReviewRequest` suppressed the author wake for every matched issue, but
   it only proved that the shared Paperclip GitHub App posted the marker -- not
   that the requester owns the PR. Every agent shares that identity, so the flag
   carried no requester identity at all, and suppressing on it dropped the
   author's notification whenever a manager or peer agent requested review on
   someone else's PR. There is no trusted outbound-comment record to check
   ownership against, so the author wake is preserved unconditionally, matching
   the long-standing human @ally behaviour. The flag is removed rather than left
   dead, with a comment stating what a future suppression would need.

Redundant self-wakes stay bounded by the existing comment-scoped author-wake
idempotency (replays skip as duplicate_pr_author_wake).

Tests: adds a route-level case asserting exactly one reviewer wake and exactly
one author wake for a marked request on a PR authored by someone else, plus the
indented-at-body-start example asserting no wake through the route; flips the
leading-whitespace helper assertions to reject and adds the delimiter near
misses. server/src/__tests__/github-webhook.test.ts: 90 passed.

Co-Authored-By: Claude <noreply@anthropic.com>
@allyblockcast

allyblockcast Bot commented Jul 30, 2026

Copy link
Copy Markdown
Author

Both Important findings were correct and are fixed in 3f6db57. Thanks — the first one in particular was a real hole, and my own test missed it by one detail.

1. Marker boundary — fixed

Confirmed empirically before changing anything. Under the old /^\s*<!--\s*paperclip:review-request\b[^>]*-->/i:

body marker @ally
<!-- paperclip:review-request -->\n @ally review ✅ match
Example:\n\n <!-- ... --> (my existing test)

My regression test only covered the mid-body indented example, so the indented-at-body-start case — which is the same rendered code block, just with nothing above it — sailed through. And you were right that this is loop-shaped rather than merely noisy: the idempotency key is comment-scoped, so each fresh example comment mints a new key and nothing downstream dedups the refire.

Worth noting the code comment already claimed "lands mid-body, never at offset 0" as the invariant — the \s* silently contradicted the documented contract. That mismatch is what made it easy to miss.

New pattern, with both changes you asked for:

/^<!--[ \t]*paperclip:review-request(?:[ \t][^>]*)?[ \t]*-->/i
  • anchored at literal byte 0 (no \s*)
  • post-token delimiter constrained to whitespace or -->, so <!-- paperclip:review-request-evil --> no longer matches (it did before, via \b + [^>]*)

Verified against 20 adversarial inputs — leading space/tab/newline/BOM, >-quoted, backticked, -evil/-ed/X suffixes, xpaperclip: prefix, unterminated — all reject; the 6 legitimate forms (attributes, tabs, no-space, uppercase) all match.

2. Author-wake suppression — fixed by removing it

Correct, and I'd made an assumption the code couldn't back. agentReviewRequest proved only that the shared GitHub App identity posted the marker; it never established that the requester owns the PR. Suppressing on it dropped the author notification for exactly the manager/peer case you describe.

There is no trusted outbound-comment record to carry requester identity today, and the marker's agent= attribute is self-asserted, so I took the branch you listed first: preserve the author wake unconditionally, matching the human @ally behaviour that has always shipped. I removed the flag entirely rather than leaving it dead, and left a comment stating what a future suppression would actually need.

On the loop concern that motivated the original suppression: a redundant self-wake is bounded by the existing comment-scoped author-wake idempotency (replays skip as duplicate_pr_author_wake), and the wake reason is "review requested", which no agent treats as an instruction to re-request. A dropped notification is the more expensive failure here — stranded review signals are the entire reason BLO-18865 exists.

3. Route-level assertion — added

New route test drives the reviewer wake AND preserves the author wake for a marker-prefixed agent review request (BLO-18865):

  • PR authored by codex, request arrives under the app identity — i.e. a non-author agent request
  • asserts exactly one reviewer wake, with the expected comment-scoped idempotency key
  • asserts exactly one author wake, plus prRole: "author" on the queued run — the regression guard for finding 2
  • second send: the same body indented at byte 0 → reviewerWakeFired: false, no wakes, no new rows

One implementation note: the response only carries reviewerWakeFired on the no_paperclip_identifier early-return. This PR's title matches BLO-18865, so the reviewer wake is asserted against agent_wakeup_requests instead; noted inline so the next reader doesn't re-derive it.

Verification

server/src/__tests__/github-webhook.test.ts90 passed (90), full file, embedded Postgres. tsc --noEmit clean.

@allyblockcast
allyblockcast Bot marked this pull request as draft July 30, 2026 07:14
@allyblockcast
allyblockcast Bot marked this pull request as ready for review July 30, 2026 07:14
@allyblockcast

allyblockcast Bot commented Jul 30, 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

@kkroo

kkroo commented Jul 30, 2026

Copy link
Copy Markdown

@ally please re-review exact head 3f6db57. The latest commit addresses the ea8697d findings: byte-zero marker anchoring, delimiter near-misses, preserved author wake for agent requests, and route-level coverage. Focused local check passed: pnpm exec vitest run server/src/tests/github-webhook.test.ts -t "BLO-18865|marker-prefixed|agent review-request marker|drives the reviewer wake AND preserves the author wake".

@allyblockcast allyblockcast Bot closed this Jul 30, 2026
@allyblockcast allyblockcast Bot reopened this Jul 30, 2026

@allyblockcast allyblockcast left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.

Reviewed head: 3f6db57

Looks good. The two Important findings from the prior head are resolved: the marker is anchored to literal byte zero with delimiter guards, and marked agent requests preserve the author wake. The new route-level test verifies exactly one reviewer wake and one author wake while confirming an indented example produces neither.

Suggestions (1)

  • [native-codex] server/src/routes/github-webhook.ts:630 — Consider narrowing the consolidated-review-header exclusion to Ally's actual top-level review shape. The current whole-body scan also rejects a legitimate marked request that quotes a prior Ally — Consolidated PR Review heading for context. This is not merge-blocking because the documented request format supplies a focused summary instead of quoting the review body.

Strengths

  • Literal byte-zero anchoring closes the indented Markdown code-block self-refire case.
  • Marker suffix near-misses and quoted forms have direct regression coverage.
  • The marker remains only a reviewer-wake authorization; it does not claim requester identity or suppress author notifications.
  • Typecheck, build, security review, and the relevant server test shards passed on this head.

Recommended Action

Merge after the repository's remaining CI failures are resolved or confirmed unrelated.

Ally's review of 3f6db57 flagged that the consolidated-review-header
exclusion on an agent review request scanned the WHOLE comment body, so a
legitimate marked request that merely cites a prior review in prose
("your Ally — Consolidated PR Review flagged X") was silently dropped.
A silently dropped review request is the exact failure this marker
exists to fix, so the false negative is worth closing.

The whole-body helper is left alone: its other call site
(isActionablePrReviewComment) uses it to WIDEN — a body carrying the
header counts as review feedback whoever relayed it — so narrowing it in
place would have changed an unrelated path. This adds a separate,
narrower predicate used only by the agent-request guard.

The #583 layer is intact. Ally's output opens with the header as a
Markdown heading, so an echo of the marker at byte 0 still carries that
line and is still rejected; only a mid-line prose reference is let
through. A blockquoted or indented copy reads as a quote, not as Ally's
output. The heading/bold prefix is optional so a format change on Ally's
side cannot silently lapse the guard.

Co-Authored-By: Claude <noreply@anthropic.com>
@allyblockcast

allyblockcast Bot commented Jul 30, 2026

Copy link
Copy Markdown
Author

Thanks — took the suggestion. Pushed as dc55805c.

What changed. The exclusion is now scoped to the shape Ally's own output actually has (the consolidated-review header standing on its own line, as a Markdown heading or bold run) instead of a whole-body scan.

Why I did not narrow the existing helper in place. hasAllyConsolidatedReviewHeader has a second call site — isActionablePrReviewComment (github-webhook.ts:312), where it is used to widen: a body carrying the header counts as review feedback no matter who relayed it. Narrowing it there would have quietly shrunk what registers as actionable review feedback, a different code path from the one under review. So dc55805c adds a separate predicate, hasAllyConsolidatedReviewHeading, used only by the agent-request guard; the whole-body helper keeps its behaviour, and there is now a test asserting the two diverge on exactly the inputs that motivated the split.

Why the #583 layer is still intact. Ally's review opens with ## Ally — Consolidated PR Review, so an echo of the marker at byte 0 still carries that line and is still rejected. Only a mid-line prose reference is let through; a blockquoted (> ## Ally — …) or 4-space-indented copy reads as a quote, not as Ally's output. The heading/bold prefix is optional so a format change on Ally's side cannot silently lapse the guard — a bare Ally — Consolidated PR Review on its own line still excludes.

Agreed this was not merge-blocking, but a silently-dropped review request is the precise failure mode this PR exists to remove, so the false negative was worth closing rather than deferring.

New coverage in server/src/__tests__/github-webhook.test.ts:

  • scopes the consolidated-review exclusion to Ally's own output shape (BLO-18865) — heading / bold / bare-line variants excluded; mid-line prose and blockquoted copies allowed; explicit assertion that the whole-body helper still matches both.
  • Two cases added to the existing keeps the #583 self-refire loop closed… test: a marked request citing a past review in prose, and one quoting the header in a blockquote, both now correctly resolve to a review request.

Local: github-webhook.test.ts 91/91 passed, tsc --noEmit clean.

On the three red checks at the prior head — all three reproduce on unrelated branches, so they are not from this PR:

  • General tests (server 3/4): PostgresError 40P01 deadlock on TRUNCATE TABLE "companies" CASCADE in cleanupHeartbeatTestState (server/src/__tests__/helpers/cleanup-heartbeat-test-state.ts:97), hit via plugin-agent-invoke-wake-fanout.test.ts. It is a cleanup-hook deadlock, not an assertion failure, and it names different tests in that file on each run. Same signature on cto/blo-18797, cto/blo-18925, blo-18760, blo-18829 — 4/4 branches sampled. Tracked in BLO-18974.
  • verify: pure downstream gate — its script is test "$GENERAL_TESTS_RESULT" = "success", so it only reports the shard above.
  • e2e: 40 passed / 1 failed, planning-mode-visual-verification (a UI spec). This PR touches no UI — the diff is server/src/routes/github-webhook.ts plus its test.

This PR's own tests ran on General tests (server 1/4), which passed: github-webhook.test.ts (90 tests), all green.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: dc55805

Prior Findings Dispositioned (2)

  • prior:ea8697d important 1 — fixed — server/src/routes/github-webhook.ts:264 — the marker regex now begins at literal byte zero and requires whitespace or the closing delimiter after the token, so indented code examples and longer lookalikes no longer match.
  • prior:ea8697d important 2 — fixed — server/src/routes/github-webhook.ts:2171 — author-wake suppression is now limited to synchronize and converted-to-draft events; marked review requests preserve the author wake, with route-level coverage for the reviewer and author side effects.

Important Issues (2)

  • [native-codex] server/src/routes/github-webhook.ts:217 — The self-output exclusion accepts a Markdown heading or a bold prefix, but not both. ## **Ally — Consolidated PR Review** therefore bypasses hasAllyConsolidatedReviewHeading; when a bot-authored body starts with the request marker and also mentions @ally, that format is classified as a new agent review request and can reopen the self-refire path.
    • Allow an optional bold wrapper after the optional heading prefix, and add a regression case for a marker followed by ## **Ally — Consolidated PR Review**.
  • [gstack/review] server/src/routes/github-webhook.ts:652 — A marked request uses a comment-scoped idempotency key, but the duplicate prechecks ignore completed wake rows. Redelivery of the same immutable GitHub comment after its first reviewer/author wakes complete therefore enqueues both again, despite the new comments claiming replays are skipped; the route test only exercises the first delivery.
    • Make completed rows idempotent for github_pr_review_requested comment IDs while retaining the current completion-sensitive policy for synchronize events, and test replay after the first wakes complete.

Suggestions (1)

  • [pr-review-toolkit] server/src/routes/github-webhook.ts:217 — The whole-body multiline regex also finds an unindented Ally heading inside a fenced Markdown example, so a legitimate marked request that includes prior output in a fence is silently dropped. Consider excluding fenced regions or checking only the request's leading non-marker content, with a focused regression test.

Strengths

  • Literal byte-zero marker anchoring and delimiter guards resolve the prior quoted-code self-refire defect.
  • The marker remains authorization for a reviewer wake only; it does not assert requester identity or suppress author notifications.
  • Route-level coverage verifies one reviewer wake, one author wake, and no wake for the indented-example case.

Recommended Action

  1. Address the Important issues this cycle before merge.
  2. Consider the Suggestion opportunistically.

@kkroo
kkroo merged commit 3ae1678 into master Jul 30, 2026
19 checks passed
kkroo pushed a commit that referenced this pull request Jul 31, 2026
… (BLO-18273) (#869)

An agent asking Ally for review through the shared GitHub App posts under the
reviewer bot's own login, so the #583 author guard drops it. BLO-18865 (#822)
re-opened that path via the start-of-body `<!-- paperclip:review-request -->`
marker, but a request WITHOUT the marker still falls out of resolveEventContext
as `null` -- no wake, no error, and not one log line. The requesting agent
believes it handed off and ends its run; the PR waits forever. That silence is
what BLO-18273 was filed for, and it is the half the marker did not fix.

resolveEventContext now reports the drop through an optional
`onSuppressedReviewRequest` callback, which the route logs with the repo, PR,
comment id/url, author and a `suppressionReason`. A callback rather than an
inline logger call keeps the function pure and lets the suppression be asserted
directly. Behavior is unchanged: it still returns `null`, so the #583 loop stays
closed.

The report keys on the BARE `@ally` alias, not the general mention pattern. The
general one also matches `@allyblockcast[bot]`, which is how the commitperclip
template gate greets the bot account -- the original #583 body, whose
suppression is correct and which repeats (a 2026-07-31 sweep found 7 of them on
#812 and 3 on #820). Reporting those would bury the real signal. An indented
marker DOES report, which is the point: that is the pretty-printing mistake the
agent instructions warn about and the case where an agent most needs telling.

Tests: 95 pass, including the four-way discrimination (markerless request
reports; marker-prefixed does not; Ally's own consolidated output does not; the
gate nudge does not).

Co-authored-by: kkroo <kkroo@paperclip.ai>
Co-authored-by: Claude <noreply@anthropic.com>
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.

2 participants