fix(db): preserve linked-issue claim on a sparse PR sync payload - #3636
Conversation
A narrower webhook event's embedded pull_request sub-object can omit body entirely (undefined) rather than reporting it as explicitly empty (null/""). Re-deriving linked issues from an absent body was silently wiping an already-correctly-claimed linked issue -- and resetting its claim timestamp -- on any such upsert, which could make the gate report "no linked issue" on a PR that clearly cited one in a prior, fuller sync. Falls back to the existing stored value only when body is genuinely absent; an explicit empty body still clears the claim as before.
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-05 20:51:29 UTC
⏸️ Suggested Action - Manual Review
Review summary Nits — 6 non-blocking
Concerns raised — review before merging
Review context
Contributor next steps
Signal definitions
🟩 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.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3636 +/- ##
=======================================
Coverage 93.23% 93.23%
=======================================
Files 314 314
Lines 31952 31953 +1
Branches 11698 11700 +2
=======================================
+ Hits 29789 29790 +1
Misses 1517 1517
Partials 646 646
🚀 New features to boost your workflow:
|
Summary
pull_requests.linked_issues_jsonwas correctly[1957]from the moment the PR was created, and that this repo'slinkedIssueGateModeisadvisory(so the live open-reference fetch path never runs) — ruling out both a live-fetch bug and a genuinely-empty body as the cause.upsertPullRequestFromGitHub'sonConflictDoUpdateunconditionally re-deriveslinkedIssuesJson/linkedIssueClaimedAtfrom the current payload'sbodyon every sync.GitHubPullRequestPayload.bodyis typedstring | nullspecifically because some callers legitimately don't have it — a narrower webhook event's embeddedpull_requestsub-object can omitbodyentirely (undefined), distinct from a genuinely empty description (null/""). Re-deriving from an absent body silently wiped an already-correctly-claimed linked issue (and reset its claim timestamp, sinceresolveLinkedIssueClaimedAttreats an emptylinkedIssuesarray as "nothing claimed").pr.body === undefinedand a prior row already exists, fall back to the already-storedlinkedIssuesJsoninstead of re-deriving from the absent body. An explicitly empty body (null/"") — a contributor genuinely clearing their PR description — still clears the claim exactly as before; this only guards the "field is absent" case the type system already models.No linked issue for this one — it's a narrowly-scoped correctness fix for a confirmed production incident (evidence: the incident report, the DB query results, and the type signature all cited above); see the Notes section for the full investigation trail.
Scope
type(scope): short summaryConventional Commit format.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally — 100% line + branch coverage on every changed line, verified via a targeted v8 coverage pass (both branches of the newpr.body === undefined && existingClaimRows[0]condition, in both itslinkedIssuesandlinkedIssuesJsonuses) in addition to the full unsharded run.npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderatetest/unit/db-parsers.test.ts— (1) a sparse re-sync (bodyabsent) preserves an already-claimed linked issue and its claim timestamp, including the function's own return value; (2) an explicit empty body (null) still clears the claim, proving the fix doesn't over-broaden; (3) a sparse sync on a brand-new PR (nothing to preserve) falls through to the pre-existing empty-default behavior. Also re-ran the full existing test suite affected by this widely-used function (npm run test:changed, 6000+ tests, 235+ files) with zero regressions.Safety
apps/gittensory-uichanges.)UI Evidencesection below with screenshots. (N/A — no visible UI change; this is an internal DB-write correctness fix.)Notes
Investigation trail (for reviewer context, not part of the code change):
resolveLinkedIssueHasOpenReference/fetchLinkedIssueFacts) as the cause:JSONbored/gittensory's ownlinkedIssueGateModeisadvisory, so that fetch path is never invoked (resolveLinkedIssueAdvisoryContextshort-circuits toPromise.resolve(true)).src/github/client.ts):/issues/{number}isn't classified into any cached class (githubCacheClassForUrlreturnsnullfor it), so it can't be served stale from Redis; the volatile single-flight coalescer that DOES apply cleans up its map entry synchronously on settle, so it can't replay a 19-minute-old result either.pull_requeststable thatlinked_issues_jsonwas"[1957]"essentially from the same instant ascreated_at— the correct value was captured and persisted immediately; something later wiped the in-memory read used for one evaluation pass, which is what led to theupsertPullRequestFromGitHubonConflictDoUpdateoverwrite path above.