You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The per-PR TYPE label decision (gittensor:bug / gittensor:feature / gittensor:priority) can still silently and permanently downgrade a correctly-propagated gittensor:feature/gittensor:priority label back to gittensor:bug right after merge — via a third, distinct mechanism from the two previously fixed (#4528, #4816). This directly strips a contributor's reward multiplier and requires manual correction to notice.
Confirmed live right now:#4818 carries gittensor:bug even though its linked issue (#2192) carries gittensor:feature. It should read gittensor:feature.
This issue: a narrower, still-live race that neither prior fix covers. Confirmed via production audit-log correlation (type_label_decision events + webhook delivery records) that the fix(review): never let an inconclusive linked-issue recheck downgrade a propagated label #4816 fix is deployed and working — it eliminated the high-frequency recurrence — but this one mechanism still gets through.
Root cause
handlePullRequestWebhookEvent (src/queue/processors.ts:5577) is the shared entry point for every event that carries a payload.pull_request sub-object — not just pull_request itself, but also pull_request_review, pull_request_review_comment, and pull_request_review_thread (confirmed at the router, src/queue/processors.ts:6283; shouldProcessPullRequestPublicSurface, src/queue/processors.ts:6481, explicitly opts pull_request_review: submitted/edited/dismissed into reaching the public-surface/type-label pipeline).
It builds its pr object via upsertPullRequestFromGitHub(env, repoFullName, payload.pull_request) (processors.ts:5639) and threads that straight into maybePublishPrPublicSurface without ever re-verifying it live. pr.mergedAt therefore reflects whichever webhook happened to trigger this specific pass — not the PR's actual current state.
When a maintainer approves and merges within milliseconds of each other (a single "approve and merge" action, or two quick clicks), GitHub fires both a pull_request_review (submitted) webhook and a pull_request (closed) webhook almost simultaneously. The review webhook's embedded pull_request snapshot is taken at review time — a few ms before the merge — so it reports merged_at: null. If that job then gets delayed behind other work in the queue (confirmed happening under real load) long enough for the actual merge to complete and its linked issue to auto-close, the review-triggered pass runs its type-label recheck against a genuinely-closed issue while its own prMergedAt still reads null.
isLinkedIssueTrustworthy (src/review/linked-issue-label-propagation-fetch.ts:79, from #4816) requires prMergedAt !== null to trust a closed linked issue. With prMergedAt null, it correctly-by-its-own-logic refuses to trust the closure — this is by design, the same check that blocks a PR from opportunistically citing an issue someone else already closed. The bug is that this specific pass's prMergedAt is stale input, not a true "PR isn't merged yet." resolveIssueLabelsForPropagation then returns a confirmed-empty result silently (src/review/linked-issue-label-propagation-fetch.ts:139 — this specific early return has no logging, unlike every other branch in that function), the caller falls through to the title heuristic (deriveKindFromTitle, src/settings/pr-type-label.ts:40), and — since that heuristic requires a literal action verb (add/create/enable/implement/integrate/introduce/launch/support/wire) in the PR title's subject and most feature titles don't happen to contain one — it resolves bug.
Webhook delivery records show a pull_request_review (submitted) delivery received 39ms before the pull_request (closed) delivery for the same PR.
The review-triggered job took 5.3 seconds to finish processing (consistent with real queue backlog at that moment — confirmed via independent queue-admission-deferral log lines in the same window) and its type_label_decision (applied:true, labels:["gittensor:bug"], source:"title") landed squarely inside that window, ~16s after the merge.
No linked_issue_label_propagation_inconclusive / _filtered log line appears for this decision — matching the silent, no-log early-return path described above exactly.
The parallel pull_request:closed webhook (the one with the correct, non-stale merged_at) finished in 1.28s but produced no visible type_label_decision of its own for this PR in the same window — it most likely lost the per-PR actuation lock race to the slower review-triggered job.
Not a duplicate/legacy label-writer — audited every ensurePullRequestLabel/removePullRequestLabel call site in src/. Exactly one writes gittensor:bug/feature/priority (processors.ts:7882-8008, inside maybePublishPrPublicSurface). No second, conflicting implementation exists.
Not the sweep/cross-reference re-review path (reReviewStoredPullRequest, processors.ts:3320) — it hard-bails whenever the PR isn't state === "open" (checked twice: once against the stored row, once against a fresh live fetch), so a closed/merged PR can never reach the type-label block through that path.
Scale
Across a 48-hour production sample (all type_label_decision events where a propagation_exclusive/propagation_additive decision was immediately followed by a title-sourced decision with a different label, for the same PR): 62 such transitions total, but only 1 is still wrong on live GitHub right now (#4818) — the other 61 were later corrected by a subsequent webhook/sweep pass recomputing a fresh, correct decision before anyone noticed. That self-healing is incidental, not guaranteed — nothing currently detects or backfills a PR that never receives a correcting follow-up pass, and a PR whose last-ever label mutation happens to be this race stays wrong indefinitely (as #4818 has).
Expected behavior
A PR's gittensor:bug/gittensor:feature/gittensor:priority label always reflects its linked issue's current label via propagation, regardless of which specific webhook event happens to trigger the recompute pass, and regardless of queue delay/ordering between related webhook deliveries for the same merge.
Actual behavior
A pull_request_review (or pull_request_review_comment / pull_request_review_thread) webhook whose embedded PR snapshot predates an imminent merge can, if delayed by queue backlog, silently and permanently downgrade the PR's label after the fact — with no log signal distinguishing it from a legitimate "issue closed independently, not by this PR" case.
Requirements
The type-label decision must not treat "this pass's own webhook-embedded pr.mergedAt is null" as proof the PR isn't merged. When the linked issue is confirmed closed but this pass's prMergedAt is null, that is an ambiguous state, not a confirmed negative.
The fix should be scoped so it only pays for an extra live check in this one narrow ambiguous branch — not on every type-label pass — to avoid adding GitHub API cost to the common path.
Add the logging this specific silent branch is currently missing (src/review/linked-issue-label-propagation-fetch.ts:139) so a future recurrence is diagnosable from logs alone, matching every sibling branch in that function.
Code fix in src/review/linked-issue-label-propagation-fetch.ts (and/or src/queue/processors.ts at the type-label call site) implementing the requirements above.
Regression test(s) covering the race for at least pull_request_review, and ideally all four affected event types.
No PR-family webhook, regardless of type or processing delay, can strip a correctly-propagated gittensor:feature/gittensor:priority label after merge. The existing tri-state (confirmed / inconclusive) design in linked-issue-label-propagation-fetch.ts is extended to correctly classify this case instead of mis-classifying it as confirmed-absent.
Summary
The per-PR TYPE label decision (
gittensor:bug/gittensor:feature/gittensor:priority) can still silently and permanently downgrade a correctly-propagatedgittensor:feature/gittensor:prioritylabel back togittensor:bugright after merge — via a third, distinct mechanism from the two previously fixed (#4528, #4816). This directly strips a contributor's reward multiplier and requires manual correction to notice.Confirmed live right now: #4818 carries
gittensor:bugeven though its linked issue (#2192) carriesgittensor:feature. It should readgittensor:feature.Area
GitHub App (webhook processing / type-label decision)
History
type_label_decisionevents + webhook delivery records) that the fix(review): never let an inconclusive linked-issue recheck downgrade a propagated label #4816 fix is deployed and working — it eliminated the high-frequency recurrence — but this one mechanism still gets through.Root cause
handlePullRequestWebhookEvent(src/queue/processors.ts:5577) is the shared entry point for every event that carries apayload.pull_requestsub-object — not justpull_requestitself, but alsopull_request_review,pull_request_review_comment, andpull_request_review_thread(confirmed at the router,src/queue/processors.ts:6283;shouldProcessPullRequestPublicSurface,src/queue/processors.ts:6481, explicitly optspull_request_review: submitted/edited/dismissedinto reaching the public-surface/type-label pipeline).It builds its
probject viaupsertPullRequestFromGitHub(env, repoFullName, payload.pull_request)(processors.ts:5639) and threads that straight intomaybePublishPrPublicSurfacewithout ever re-verifying it live.pr.mergedAttherefore reflects whichever webhook happened to trigger this specific pass — not the PR's actual current state.When a maintainer approves and merges within milliseconds of each other (a single "approve and merge" action, or two quick clicks), GitHub fires both a
pull_request_review(submitted) webhook and apull_request(closed) webhook almost simultaneously. The review webhook's embeddedpull_requestsnapshot is taken at review time — a few ms before the merge — so it reportsmerged_at: null. If that job then gets delayed behind other work in the queue (confirmed happening under real load) long enough for the actual merge to complete and its linked issue to auto-close, the review-triggered pass runs its type-label recheck against a genuinely-closed issue while its ownprMergedAtstill readsnull.isLinkedIssueTrustworthy(src/review/linked-issue-label-propagation-fetch.ts:79, from #4816) requiresprMergedAt !== nullto trust a closed linked issue. WithprMergedAtnull, it correctly-by-its-own-logic refuses to trust the closure — this is by design, the same check that blocks a PR from opportunistically citing an issue someone else already closed. The bug is that this specific pass'sprMergedAtis stale input, not a true "PR isn't merged yet."resolveIssueLabelsForPropagationthen returns a confirmed-empty result silently (src/review/linked-issue-label-propagation-fetch.ts:139— this specific early return has no logging, unlike every other branch in that function), the caller falls through to the title heuristic (deriveKindFromTitle,src/settings/pr-type-label.ts:40), and — since that heuristic requires a literal action verb (add/create/enable/implement/integrate/introduce/launch/support/wire) in the PR title's subject and most feature titles don't happen to contain one — it resolvesbug.Evidence chain (PR #4818)
2026-07-11T02:26:25Z; PR feat(ui): confidence-calibration curve card on the analytics dashboard (#2192) #4818 merged at2026-07-11T02:26:24Z(same event, ~1s apart).pull_request_review(submitted) delivery received 39ms before thepull_request(closed) delivery for the same PR.type_label_decision(applied:true, labels:["gittensor:bug"], source:"title") landed squarely inside that window, ~16s after the merge.linked_issue_label_propagation_inconclusive/_filteredlog line appears for this decision — matching the silent, no-log early-return path described above exactly.pull_request:closedwebhook (the one with the correct, non-stalemerged_at) finished in 1.28s but produced no visibletype_label_decisionof its own for this PR in the same window — it most likely lost the per-PR actuation lock race to the slower review-triggered job.Ruled out
ensurePullRequestLabel/removePullRequestLabelcall site insrc/. Exactly one writesgittensor:bug/feature/priority(processors.ts:7882-8008, insidemaybePublishPrPublicSurface). No second, conflicting implementation exists.reReviewStoredPullRequest,processors.ts:3320) — it hard-bails whenever the PR isn'tstate === "open"(checked twice: once against the stored row, once against a fresh live fetch), so a closed/merged PR can never reach the type-label block through that path.Scale
Across a 48-hour production sample (all
type_label_decisionevents where apropagation_exclusive/propagation_additivedecision was immediately followed by atitle-sourced decision with a different label, for the same PR): 62 such transitions total, but only 1 is still wrong on live GitHub right now (#4818) — the other 61 were later corrected by a subsequent webhook/sweep pass recomputing a fresh, correct decision before anyone noticed. That self-healing is incidental, not guaranteed — nothing currently detects or backfills a PR that never receives a correcting follow-up pass, and a PR whose last-ever label mutation happens to be this race stays wrong indefinitely (as #4818 has).Expected behavior
A PR's
gittensor:bug/gittensor:feature/gittensor:prioritylabel always reflects its linked issue's current label via propagation, regardless of which specific webhook event happens to trigger the recompute pass, and regardless of queue delay/ordering between related webhook deliveries for the same merge.Actual behavior
A
pull_request_review(orpull_request_review_comment/pull_request_review_thread) webhook whose embedded PR snapshot predates an imminent merge can, if delayed by queue backlog, silently and permanently downgrade the PR's label after the fact — with no log signal distinguishing it from a legitimate "issue closed independently, not by this PR" case.Requirements
pr.mergedAtis null" as proof the PR isn't merged. When the linked issue is confirmed closed but this pass'sprMergedAtis null, that is an ambiguous state, not a confirmed negative.pull_request,pull_request_review,pull_request_review_comment,pull_request_review_thread), not just the one implicated in feat(ui): confidence-calibration curve card on the analytics dashboard (#2192) #4818.src/review/linked-issue-label-propagation-fetch.ts:139) so a future recurrence is diagnosable from logs alone, matching every sibling branch in that function.pull_requestPR-family webhook with a stale/nullmerged_atracing a real merge whose linked issue is already closed).Deliverables
src/review/linked-issue-label-propagation-fetch.ts(and/orsrc/queue/processors.tsat the type-label call site) implementing the requirements above.pull_request_review, and ideally all four affected event types.gittensor:bug→gittensor:feature) — a data fix, independent of the code fix.Expected outcome
No PR-family webhook, regardless of type or processing delay, can strip a correctly-propagated
gittensor:feature/gittensor:prioritylabel after merge. The existing tri-state (confirmed / inconclusive) design inlinked-issue-label-propagation-fetch.tsis extended to correctly classify this case instead of mis-classifying it as confirmed-absent.Out of scope