fix(github-webhook): close two Markdown escapes in owning-reference parsing (BLO-20886) - #1051
Conversation
…arsing (BLO-20886)
Ally's round-4 review found the exact-head scanner still reachable by two
ordinary Markdown forms. Both reproduce, and both let an issue named only in
an example capture an author-directed "push a follow-up commit" wake -- this
ticket's own defect, reached through the parser rather than the tier order.
- A fence nested in a list item (`- ```md`) never opened a fence, because the
opening line starts with the list marker. The indented `Refs:` line inside
the example stayed visible to the label match. Fence openers are now
recognized after an optional list marker.
- A line inside an open fence repeating the marker with an info string
(``` js) closed it, because the comparison was length-only. CommonMark
allows only whitespace after a closing fence's marker run, so that line is
content; treating it as the close reopened the rest of the block. Closing
now requires a same-or-longer run followed by whitespace only.
- A multi-line HTML comment could declare an owner. A `Refs:` line between
`<!--` and `-->` resolved its identifier while the PR rendered no ownership
declaration at all -- a misroute with no visible cause to debug from, and
the repo's own PULL_REQUEST_TEMPLATE.md ships multi-line comments in every
section. Comments are now stripped before the label match, with state
carried across lines.
Ambiguity fails closed in every case: an unterminated fence or `<!--` swallows
the rest of the body, so the caller drops the wake or routes it to the
reviewer rather than guessing an owner.
Regressions cover all four shapes plus the controls that must keep resolving
(a closed list fence releases the lines after it; a visible owner beside a
commented decoy resolves to the visible one). Verified failing before the fix:
`expected { owning: [ 'BLO-999' ] } to deeply equal { owning: [] }`.
Verifying signal: `General tests (server N/4)` running
server/src/__tests__/github-webhook.test.ts -- the two new cases
"keeps owning-looking text unreachable inside list-nested fences and unclosed
fences (BLO-20886)" and "never lets an HTML comment declare an owner
(BLO-20886)". 202 tests pass across github-webhook + heartbeat-context-summary.
Co-Authored-By: Claude <noreply@anthropic.com>
Ally flagged the new `refetchInterval` as running alongside the pre-existing empty-queue `setTimeout`. It does. The effect reads as a one-shot, but its deps include `query.dataUpdatedAt`, so every refetch re-ran it and re-armed the timer -- a second full 2s poll loop. Measured before removing it, because the stated harm turned out to be wrong: the two timers both re-arm off the same fetch completion, so they converge and react-query dedupes the pair. Poll gaps are 2001ms with and without the effect -- identical. There were no redundant requests to remove in the visible case, so no call-count or gap assertion can tell the variants apart, which is also why the previous `>=` assertions could not. The real harm is narrower and in the other case: the timeout polled every 2s unconditionally, silently overriding the deliberate 20s backoff `refetchInterval` applies when `emptyState === "hidden"` and the card renders nothing. Deleting the effect leaves one mechanism and makes that backoff take effect -- the only observable behavior change here. The hidden case is therefore what the regression pins, since it is the one that discriminates: it fails on the two-timer variant with "expected vi.fn() to be called 2 times, but got 3 times". The visible test additionally asserts the ~2s cadence from measured inter-call gaps rather than a bare `>=` count. Verifying signal: `General tests (workspaces-*)` running ui/src/pages/apps/ReviewQueueCard.test.tsx -- new case "leaves a hidden empty queue on the slow interval instead of fast-polling it". 6 tests pass. Co-Authored-By: Claude <noreply@anthropic.com>
|
🔗 Paperclip issue: BLO-20886 |
1 similar comment
|
🔗 Paperclip issue: BLO-20886 |
|
@ally please review head bf823a4 — this is the follow-up to your round-4 review on #962, opened separately because #962 is branch-locked in the merge queue (position 36). Both Important findings reproduced exactly and are fixed here:
Review focus: (1) is the fail-closed direction right — an unterminated fence or |
|
Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention: Missing or incomplete:
Once updated, push a new commit and these checks will re-run automatically. — commitperclip |
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Critical Issues (0)Important Issues (3)
Suggestions (2)
Strengths
Recommended Action
|
kkroo
left a comment
There was a problem hiding this comment.
Reviewed the parser hardening and ReviewQueueCard polling cleanup. The ownership parser now ignores owning-looking labels inside list-nested fences, unterminated fences, and HTML comments, with regression coverage for both safe drops and visible-owner controls. The UI timer cleanup is covered by the hidden empty-queue polling test. No active review threads remain; review and security-review are green.
57561d9
into
sre/blo-20886-pr-review-wake-routing
Thinking Path
Linked Issues or Issue Description
What Changed
extractOwningLabeledIdentifiersrecognizes a fence opener after an optional list marker, so a fence nested in a bullet (- ```md) actually opens.Refs:line between<!--and-->cannot declare an owner.<!--swallows the rest of the body, so the caller drops the wake or routes it to the reviewer rather than guessing.ReviewQueueCardpolls an empty queue on one timer instead of two.Verification
server/src/__tests__/github-webhook.test.ts— two new cases covering list-nested fences, unclosed fences, trailing-content closing fences, and single/multi-line/unterminated HTML comments, plus the controls that must keep resolving. Verified failing before the fix:expected { owning: [ 'BLO-999' ] } to deeply equal { owning: [] }.github-webhook+heartbeat-context-summary.ui/src/pages/apps/ReviewQueueCard.test.tsx— 6 pass; the new hidden-queue case fails on the two-timer variant withexpected "vi.fn()" to be called 2 times, but got 3 times.tsc --noEmitclean for bothserverandui.Risks
Low. This only narrows what counts as an ownership declaration, and every ambiguous case fails closed to "no owning reference" — which the caller already handles by dropping the wake or routing it to the reviewer, never by guessing an owner.
Worth stating plainly: one of the three review claims did not survive measurement. Ally reported the duplicate
ReviewQueueCardtimers as issuing redundant requests. They do not — both re-arm off the same fetch completion, so they converge and react-query dedupes the pair; measured poll gaps are 2001ms with and without the second timer, identical. The real harm is narrower: the stray timer polled every 2s unconditionally, overriding the deliberate 20s backoff foremptyState === "hidden". That is the one observable behavior change here, and it is what the new test pins, because the visible cadence cannot tell the variants apart.Separate from #962 because #962 is in the merge queue and its branch is locked. Dequeuing to push these would forfeit its position on a ~17h queue and delay both the fleet-wide routing fix and #999, which is stacked on it — to harden a parser that does not exist on master yet, so nothing currently live regresses by landing in this order. Based on #962's head; GitHub will retarget this to
masterwhen #962 merges.Model Used
Checklist
Fixes: #/Closes #/Refs #OR (b) described the issue in-PR following the relevant issue template