Skip to content

fix(review): stop the deterministic type-label mislabel from #5233's broken closure check#5407

Merged
JSONbored merged 1 commit into
mainfrom
worktree-label-propagation-fix
Jul 12, 2026
Merged

fix(review): stop the deterministic type-label mislabel from #5233's broken closure check#5407
JSONbored merged 1 commit into
mainfrom
worktree-label-propagation-fix

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

  • Root-caused today's PR type-label mislabeling recurrence to a broken GraphQL/REST mismatch in the closure-verification check added by fix(review): verify linked issue closure source #5233 this morning: fetchLinkedIssueClosedByPullRequest read GitHub's REST /issues/{n}/timeline looking for a source.issue field on closed-type events, but that field structurally never appears there (confirmed against three live production examples) — only on cross-referenced events. This made the check always return not_closed_by_pull_request, even for a completely legitimate same-PR close, turning every "Closes #N" merge into a guaranteed (not just occasional) title-heuristic mislabel. Replaced it with GraphQL's Issue.timelineItems -> ClosedEvent.closer field, which correctly and directly identifies the closing PR — verified empirically against three live issues before writing the fix.
  • Fixed a separate latent precedence bug in resolvePrTypeLabel: when a linked issue (or several disagreeing linked issues) carried both gittensor:bug and gittensor:feature, the exclusive-mapping resolution picked whichever was declared FIRST in .gittensory.yml's mappings: array — currently bug, listed before feature — so bug always won regardless of value, backwards from feature's higher scoring weight. Now the LAST-declared exclusive match wins, and operators are documented to declare exclusive mappings in ascending precedence order (matching the existing bug→feature→priority default order, so no config change is needed for the three gated repos). Updated .gittensory.yml.example and its byte-identical twin config/examples/gittensory.full.yml, which still described the old first-wins rule.
  • Fixed a structural gate asymmetry: maybeReReviewOnLinkedIssueChange (wakes a PR when its linked issue is relabeled) only checked isConvergenceRepoAllowed, unlike the periodic sweep (sweepRepoRegate), which also falls back to isAgentConfigured(settings.autonomy). A repo with real acting autonomy configured but outside the GITTENSORY_REVIEW_REPOS env allowlist could see a stale type label / hard-rule verdict indefinitely. Aligned the two gates, short-circuited so the common (already-allowlisted) case never pays for the extra settings fetch.

Closes #5385

Root cause detail

See #5385 for the full investigation, including three confirmed-live mislabeled PRs found in the ~40 minutes after this morning's #5233 deploy (#5360, #5363, #5371 — all gittensor:featuregittensor:bug) and one that additionally lost its gittensor:priority reward multiplier entirely (#5359).

Changes

  • src/github/backfill.ts: fetchLinkedIssueClosedByPullRequest rewritten to query GraphQL's ClosedEvent.closer instead of REST's /issues/{n}/timeline. Removed the now-dead timelineEventClosesIssueFromPullRequest REST-shape helper.
  • src/settings/pr-type-label.ts (+ byte-identical mirror in packages/gittensory-engine/src/settings/pr-type-label.ts): exclusive-mapping resolution now lets the LAST match win instead of the first.
  • src/queue/processors.ts: maybeReReviewOnLinkedIssueChange's gate now falls back to isAgentConfigured(settings.autonomy), short-circuited to avoid the extra fetch in the common allowlisted case.
  • .gittensory.yml.example / config/examples/gittensory.full.yml: updated the exclusive-mapping precedence doc comment to match the new last-wins behavior.
  • Tests: rebuilt every timeline-shaped mock in test/unit/linked-issue-label-propagation-fetch.test.ts and test/unit/queue-5.test.ts from GitHub's REAL GraphQL response shape (not an assumed one — the exact gap that let fix(review): verify linked issue closure source #5233 ship broken). Added a dedicated test/unit/backfill.test.ts suite directly covering every branch of the rewritten fetchLinkedIssueClosedByPullRequest (no token, malformed repo, GraphQL top-level errors array, malformed nodes, network failure, matching/non-matching/null/Commit closer) — several of these are real, confirmed-live GitHub response shapes, not hypotheticals. Updated the two pr-type-label.test.ts/pr-type-label-engine.test.ts tests that encoded the old first-match-wins precedence. Added two new queue.test.ts regression tests for the autonomy-fallback gate (wakes via fallback when unlisted-but-configured; stays dormant when genuinely inert).

Test plan

  • npm run typecheck — clean
  • npm run test:ci (full local gate, unsharded coverage) — see CI
  • All directly-affected test files independently re-run and green: pr-type-label.test.ts, pr-type-label-engine.test.ts, linked-issue-label-propagation-fetch.test.ts, queue.test.ts, queue-5.test.ts
  • Regression tests added for all three fixes, built from real captured API shapes

…broken closure check

fetchLinkedIssueClosedByPullRequest read GitHub's REST /issues/{n}/timeline looking
for a source.issue field on "closed" events, but that field never appears there --
only on cross-referenced events. Confirmed against three live production issues
(commit_id null, no source key at all). This made the check always fail, turning
a rare race into a 100%-reproducing failure on every "Closes #N" merge. Replaced
with GraphQL's Issue.timelineItems -> ClosedEvent.closer, verified empirically
against live issues.

Also fixes two related gaps found during the same investigation:
- resolvePrTypeLabel picked the exclusive bug/feature label by config array
  order (bug always won when both matched an issue's labels) instead of
  declared precedence. Now the LAST-configured exclusive match wins; operators
  declare exclusive mappings in ascending precedence order. Updated the two
  bundled example configs, which still described the old first-wins rule.
- maybeReReviewOnLinkedIssueChange only checked isConvergenceRepoAllowed,
  unlike the periodic sweep, which also falls back to
  isAgentConfigured(settings.autonomy). Aligned the two gates, short-circuited
  so the common allowlisted case never pays for the extra settings fetch.

Closes #5385
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

1 similar comment
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
gittensory-ui 17bb4a8 Commit Preview URL

Branch Preview URL
Jul 12 2026, 05:03 PM

@codecov

codecov Bot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.41%. Comparing base (0d78f24) to head (17bb4a8).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5407      +/-   ##
==========================================
- Coverage   94.45%   94.41%   -0.04%     
==========================================
  Files         552      552              
  Lines       44310    44317       +7     
  Branches    14659    14663       +4     
==========================================
- Hits        41851    41841      -10     
- Misses       1784     1801      +17     
  Partials      675      675              
Flag Coverage Δ
shard-1 44.16% <7.14%> (-0.01%) ⬇️
shard-2 34.66% <85.71%> (+0.03%) ⬆️
shard-3 31.96% <50.00%> (+0.45%) ⬆️
shard-4 31.64% <7.14%> (+0.12%) ⬆️
shard-5 32.74% <7.14%> (-0.60%) ⬇️
shard-6 43.32% <0.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...es/gittensory-engine/src/settings/pr-type-label.ts 100.00% <100.00%> (ø)
src/github/backfill.ts 97.22% <100.00%> (+0.01%) ⬆️
src/queue/processors.ts 95.63% <100.00%> (ø)
src/settings/pr-type-label.ts 98.11% <100.00%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 12, 2026
@loopover-orb

loopover-orb Bot commented Jul 12, 2026

Copy link
Copy Markdown

Warning

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏸️ Gittensory review result - manual review recommended

Review updated: 2026-07-12 18:59:19 UTC

12 files · 1 AI reviewer · no blockers · readiness 100/100 · CI green · clean

⏸️ Suggested Action - Manual Review

  • Touches a guarded path — held for manual review: This PR changes guardrail-protected path(s): packages/gittensory-engine/src/settings/pr-type-label.ts (matched packages/gittensory-engine/**), src/github/backfill.ts (matched src/github/backfill.ts), src/queue/processors.ts (matched src/queue/**).

Review summary
This PR replaces a broken REST timeline-based issue-closure check with a GraphQL Issue.timelineItems->ClosedEvent.closer query, which correctly identifies the closing PR and includes solid guard clauses (no token, malformed repo name, GraphQL errors, malformed nodes). It also fixes a real precedence bug in resolvePrTypeLabel (first-match-wins to last-match-wins for exclusive mappings, applied consistently to both the src/ and packages/gittensory-engine/ twin files) and closes a gate asymmetry in maybeReReviewOnLinkedIssueChange by adding the same isAgentConfigured(settings.autonomy) fallback sweepRepoRegate already uses, with the short-circuit correctly preserving the fast path for the common allowlisted case. Tests were updated across six files to pin every branch of the new GraphQL closure check and both mapping-precedence orders, and CI is green.

Nits — 7 non-blocking
  • Two near-identical copies of resolvePrTypeLabel (src/settings/pr-type-label.ts and packages/gittensory-engine/src/settings/pr-type-label.ts) had to be patched in lockstep — pre-existing duplication, but worth eventually consolidating into one shared module so future precedence bugs can't recur in only one copy.
  • The GraphQL query in src/github/backfill.ts is built via a template literal with JSON.stringify(owner)/JSON.stringify(name) interpolated directly into the query string rather than using GraphQL variables — safe here since owner/name come from repoFullName, but worth switching to parameterized variables for defense-in-depth if this pattern is reused elsewhere.
  • Comment updates to .gittensory.yml.example and config/examples/gittensory.full.yml are correctly kept byte-identical, but a lint/test asserting the two config docs stay in sync would prevent future drift.
  • Consider extracting the shared resolvePrTypeLabel exclusive/additive resolution logic into one package that both src/settings and packages/gittensory-engine import, to remove the twin-file maintenance burden.
  • Consider adding a comment or constant referencing GraphQL's ClosedEvent.closer union possibilities (PullRequest | Commit) directly in the type definition for future readers who may extend itemTypes.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.
  • Touches a guarded path — held for manual review — A maintainer must review and merge this change.
Signal Result Evidence
Code review ✅ No blockers 1 reviewer
Linked issue ✅ Linked #5385
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 44 registered-repo PR(s), 36 merged, 457 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 44 PR(s), 457 issue(s).
Gate result ⚠️ Not blocking Advisory; not blocking this PR.
Improvement ✅ Minor risk: clean · value: minor — Code changes are accompanied by test evidence.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 44 PR(s), 457 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
[BETA] Chat with Gittensory

Ask Gittensory a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @gittensory ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @gittensory chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @gittensory mention with a real question is routed to the closest matching read-only command automatically -- no exact syntax required.

Full command reference: https://gittensory.aethereal.dev/docs/gittensory-commands

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.

  • Re-run Gittensory review

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 12, 2026
@JSONbored
JSONbored merged commit e58f00c into main Jul 12, 2026
19 checks passed
@JSONbored
JSONbored deleted the worktree-label-propagation-fix branch July 12, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: PR type label deterministically downgraded on merge — broken GitHub timeline closure check (5th recurrence)

1 participant