Skip to content

fix: defer pr issue links backfill#171

Merged
MkDev11 merged 4 commits into
mainfrom
fix/defer-pr-issue-links-backfill
May 27, 2026
Merged

fix: defer pr issue links backfill#171
MkDev11 merged 4 commits into
mainfrom
fix/defer-pr-issue-links-backfill

Conversation

@MkDev11
Copy link
Copy Markdown
Owner

@MkDev11 MkDev11 commented May 27, 2026

Summary by CodeRabbit

  • New Features

    • Added optimized query path for recent pull requests.
  • Documentation

    • Updated documentation to clarify that Explorer, Issues, and Pulls feeds are based on Gittensor-listed repositories only.
    • Repositioned custom repository management as a legacy feature not driving main feeds.
  • Refactor

    • Consolidated repository sources to use only live Gittensor-listed repositories across all feeds and endpoints.
    • Removed custom repository inclusion from polling, metrics, and aggregation logic.
    • Enhanced cache pruning to remove stale data for repositories no longer in upstream sources.

Review Change Stack

MkDev11 added 2 commits May 27, 2026 01:10
Root cause of the Explorer page's "stuck loading skeleton" (Cloudflare
524 timeouts at the origin): `backfillPrIssueLinksIfNeeded` is called
inline by 8 API routes (issues/route, pulls/route, issues-meta,
related-issues, related-prs, gt/repos/[owner]/[name], etc.). On a
cold path where `pr_issue_links` is empty for the repo (e.g. after
PR #137's schema-purge migration), the function synchronously:

  1. SELECTs every PR + title + body for the repo
  2. Runs `extractLinkedIssues` regex over each PR's body
  3. Writes resulting links in a single sqlite transaction

For a repo with hundreds+ of PRs this can stall the Node event loop
for tens of seconds. Cloudflare gives up at 100s and returns 524 to
the client; the consumer in RepoExplorer.tsx treats !r.ok as fatal
and leaves the Issues / Pull Requests tabs stuck on their skeleton.

Fix: split the backfill into a request-path entry point that gates +
defers, and a separate `runPrIssueLinksBackfill` that does the heavy
work. The entry point now:
  - Returns immediately when the gate count > 0 (existing fast path,
    unchanged behaviour for steady state).
  - On cold start, schedules the backfill via setImmediate and
    returns 0 to the caller. A per-repo in-flight Set dedupes
    concurrent cold requests so we don't pile up serial writer-tx
    queues.

The first request after a purge / cold start returns empty linked-
issues / linked-PRs enrichment, but the route responds in
milliseconds instead of timing out. Subsequent requests (after the
deferred backfill commits) get full enrichment.

Companion to df1f09c + 2fa2d13 + 99df6b1 on this branch which catch
*throws* — those didn't help here because the underlying handler was
*hanging*, not erroring. This is the actual root-cause patch.
CodeRabbit caught a real edge case in #168: my cold-path check is
`if (existingCount > 0) return existingCount;`. For repos that
legitimately have zero linked issues (brand-new repo, or every PR
genuinely has no `Closes #N` references), `existingCount === 0` is
ALWAYS true → every request to one of the 8 calling routes schedules
another backfill → every request re-loads all PRs + bodies and
regex-extracts them, even though the previous backfill already ran
and confirmed there's nothing to write. In-flight Set only dedupes
concurrent schedules, not subsequent ones.

Fix: add a persistent marker column `pr_issue_links_backfilled_at` on
repo_meta (idempotent migration in db.ts alongside the existing
ALTER TABLE backfill columns). The cold-path check now reads the
marker too — once set, the repo is considered backfilled even with
zero rows. Marker is written by the deferred worker on successful
completion only; failures intentionally leave it null so the next
request retries.

Real cost without this fix: every request for a linkless repo runs
the full PR scan in the background. Defer keeps it off the request
path so users don't see latency, but it's wasted CPU per-request and
amplifies under traffic.

Follow-up to 725340e.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b160d447-40b3-44b1-8035-69768e6e4dc8

📥 Commits

Reviewing files that changed from the base of the PR and between 38f458a and b522b18.

📒 Files selected for processing (18)
  • src/app/api/all-repos/route.ts
  • src/app/api/issues/route.ts
  • src/app/api/poller-status/route.ts
  • src/app/api/pulls/route.ts
  • src/app/api/repo-activity/route.ts
  • src/app/api/repos/[owner]/[name]/issues/route.ts
  • src/app/api/repos/[owner]/[name]/pulls/route.ts
  • src/app/docs/page.tsx
  • src/app/issues/page.tsx
  • src/app/pulls/page.tsx
  • src/components/IssuesTable.tsx
  • src/components/NewIssuesWatcher.tsx
  • src/components/RepoExplorer.tsx
  • src/lib/api-utils.ts
  • src/lib/db.ts
  • src/lib/poller.ts
  • src/lib/repos-server.ts
  • src/lib/use-sn74-repos.ts
💤 Files with no reviewable changes (1)
  • src/lib/api-utils.ts
✅ Files skipped from review due to trivial changes (2)
  • src/app/issues/page.tsx
  • src/lib/use-sn74-repos.ts
🚧 Files skipped from review as they are similar to previous changes (14)
  • src/app/api/repos/[owner]/[name]/issues/route.ts
  • src/app/docs/page.tsx
  • src/app/api/repos/[owner]/[name]/pulls/route.ts
  • src/components/IssuesTable.tsx
  • src/app/api/issues/route.ts
  • src/app/api/all-repos/route.ts
  • src/app/api/poller-status/route.ts
  • src/app/api/repo-activity/route.ts
  • src/lib/poller.ts
  • src/lib/repos-server.ts
  • src/components/NewIssuesWatcher.tsx
  • src/app/pulls/page.tsx
  • src/components/RepoExplorer.tsx
  • src/app/api/pulls/route.ts

📝 Walkthrough

Walkthrough

This PR consolidates repository handling to live Gittensor-listed repos by removing user-repo augmentation across backend APIs, frontend components, and polling infrastructure. It introduces cache pruning for removed upstream repos, adds API endpoint guards to block untracked repos, and simplifies query logic to use only live repo weights.

Changes

User-Repo Integration Removal and Live-Repo Unification

Layer / File(s) Summary
Live-repo filtering and cache pruning infrastructure
src/lib/repos-server.ts
repos-server now prunes cache rows for repositories removed upstream, serves data from in-memory live snapshot (no DB cold-start fallback), and restricts tracked-repo checks to the live snapshot. Includes new pruning helpers and updated refresh transaction logic to delete repo_weights and related cache rows for missing upstream repos.
Poller refactoring and database semantics updates
src/lib/poller.ts, src/lib/db.ts
Poller builds the polling set solely from live repos (removes DB user-repos loading), adjusts tier-1 to use only top-weighted repos, and database documentation now describes user_repos as an authoritative mirror of the latest live sync with cache pruning for removed repos.
API endpoint guards and query simplification
src/app/api/all-repos/route.ts, src/app/api/issues/route.ts, src/app/api/repos/[owner]/[name]/issues/route.ts, src/app/api/repos/[owner]/[name]/pulls/route.ts
Adds isTrackedRepoServer guards to block untracked repos at route entry, narrows AllReposEntry.source to 'sn74' literal, removes user_repos LEFT JOINs, and simplifies weight ordering to use only repo_weights.
Pulls endpoint fast-recent query path
src/app/api/pulls/route.ts
Implements conditional fast path for recent pulls: per-repo bounded reads with in-memory author aggregation and merge/sort, falling back to SQL when eligibility conditions are not met.
Poller-status metrics and repo-activity scoping
src/app/api/poller-status/route.ts, src/app/api/repo-activity/route.ts
Scopes all metrics to current live repos using parameterized IN(...) queries; repos_total is now liveRepos.length. loadAllowedRepos builds allowed-repo map exclusively from getLiveReposAsyncServer().
API utilities and import cleanup
src/lib/api-utils.ts
resolveRepoScope now populates allowed repos solely from liveRepos; removes DB-backed user_repos augmentation and getReadDb import.
Frontend Pulls page and IssuesTable refactoring
src/app/pulls/page.tsx, src/components/IssuesTable.tsx
Removes /api/user-repos fetching and UserReposResp types; derives repo scoping and weights exclusively from SN74/live repos. Pulls page now captures error state and enables single retry, rendering explicit error message on query failure.
Frontend NewIssuesWatcher and RepoExplorer updates
src/components/NewIssuesWatcher.tsx, src/components/RepoExplorer.tsx
Removes user-repos polling and allowlist integration; derives issue-toast and repo-list allowlists exclusively from SN74/live repos. Removes left-rail "Custom" badge and clarifies URL hydration effect behavior.
User-facing documentation and copy updates
src/app/docs/page.tsx, src/app/issues/page.tsx, src/lib/use-sn74-repos.ts
Updates docs, help text, and page headers to describe scope as live Gittensor-listed repositories; repositions /manage-repos as legacy custom-repo list; removes locally-defined Pill component; clarifies useSn74Repos documentation to note both added and removed repos are reflected.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Hoppers unite, no more split roads,
One source of truth now bears all loads,
Gone are the custom-repo ways,
Live Gittensor shines through the haze,
Caches pruned, the code flows clean,
Simpler than before we've seen!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'fix: defer pr issue links backfill' is vague and does not clearly describe the actual scope of changes, which involves removing user repos from multiple endpoints and UI components. Consider a more descriptive title that captures the main change, such as 'refactor: scope repos/issues/pulls to live Gittensor list only' or 'fix: remove user-managed repos from live data feeds'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/defer-pr-issue-links-backfill

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/app/api/all-repos/route.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/app/api/issues/route.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

src/app/api/poller-status/route.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 14 others

Comment @coderabbitai help to get the list of available commands and usage tips.

@MkDev11 MkDev11 force-pushed the fix/defer-pr-issue-links-backfill branch from 38f458a to 1b0d56f Compare May 27, 2026 02:39
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/app/docs/page.tsx (1)

483-483: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove unused Pill component to clear lint warning.

Pill is defined but never used, and this is currently failing lint in CI. Please delete it (or use it) before merge.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/docs/page.tsx` at line 483, The Pill component (function Pill with
signature function Pill({ children }: { children: React.ReactNode })) is
declared but unused and causing lint failures; remove the entire Pill function
declaration (and any related unused imports/exports) from the file, or
alternatively replace its removal by using the Pill component where
intended—ensure no remaining references or unused props (children) remain so
lint passes.
src/components/RepoExplorer.tsx (1)

554-580: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle selected repo fallback when live list removes a repo.

If the current selected repo disappears from allRepos after refresh, the effect keeps a stale selection instead of switching to a valid live repo.

Suggested fix
 useEffect(() => {
   const urlRepo = searchParams?.get('repo') ?? null;
   setSelected((prev) => {
+    const prevStillLive = allRepos.some((x) => x.fullName === prev.fullName);
+    const firstLive = allRepos[0];
+
     // Prefer the URL-specified repo when present.
     if (urlRepo) {
-      if (prev.fullName === urlRepo) return prev;
+      if (prev.fullName === urlRepo && prevStillLive) return prev;
       const found = allRepos.find((x) => x.fullName === urlRepo);
-      if (!found) return prev;
+      if (!found) return prevStillLive ? prev : (firstLive ?? prev);
       selectedFromUrlRef.current = true;
       return found;
     }
     // No `?repo=` and we're still on the placeholder: promote the
     // first real repo from the live list so the page has something
     // to render once `/api/sn74-repos` lands.
-    if (!prev.fullName && allRepos.length > 0) return allRepos[0];
+    if ((!prev.fullName || !prevStillLive) && firstLive) return firstLive;
     return prev;
   });
 }, [searchParams, allRepos]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/RepoExplorer.tsx` around lines 554 - 580, The effect that sets
selected (inside RepoExplorer's useEffect using setSelected, searchParams,
allRepos and selectedFromUrlRef) doesn't handle the case where the current
prev.fullName is no longer present in allRepos, so the UI keeps a stale
selection; update the setter logic so that if there is no ?repo= (urlRepo falsy)
and prev.fullName is non-empty but cannot be found in allRepos, you promote a
valid fallback (e.g., allRepos[0]) instead of returning prev; ensure the
existing behavior for URL-specified repo remains (set selectedFromUrlRef.current
= true when found) and keep the branch that promotes the first live repo when
prev was the placeholder.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/refresh.ts`:
- Around line 323-371: Normalize the incoming repoFullName (e.g., const
normalized = repoFullName.toLowerCase().trim()) at the top of
backfillPrIssueLinksIfNeeded and use that normalized value for all gating, SQL
lookups (COUNT and repo_meta SELECT), the inFlightLinksBackfill set/has/delete
calls, the runPrIssueLinksBackfill invocation inside setImmediate, and the
INSERT/UPDATE of repo_meta so casing differences can't cause duplicate backfills
or fragmented repo_meta rows; ensure all comparisons and writes consistently use
the same normalized identifier.

In `@src/lib/repos-server.ts`:
- Around line 261-263: The early return in pruneCachedDataToLiveRepos when
liveKeys.length === 0 prevents pruning cached tables and leaves stale rows
(e.g., repo_weights) when upstream yields an empty repo set; remove that early
return and instead handle the empty liveKeys case explicitly in
pruneCachedDataToLiveRepos: when liveKeys is empty run the same delete logic but
without a WHERE IN clause (i.e., delete all rows from repo_weights and related
cache tables or use a query that matches no live keys), ensure removed is
incremented for the rows deleted, and apply the same empty-set handling to the
other pruning path referenced around the repo_weights logic (the logic mentioned
at the other location noted in the review) so both paths consistently clear
cache when liveKeys is empty.

---

Outside diff comments:
In `@src/app/docs/page.tsx`:
- Line 483: The Pill component (function Pill with signature function Pill({
children }: { children: React.ReactNode })) is declared but unused and causing
lint failures; remove the entire Pill function declaration (and any related
unused imports/exports) from the file, or alternatively replace its removal by
using the Pill component where intended—ensure no remaining references or unused
props (children) remain so lint passes.

In `@src/components/RepoExplorer.tsx`:
- Around line 554-580: The effect that sets selected (inside RepoExplorer's
useEffect using setSelected, searchParams, allRepos and selectedFromUrlRef)
doesn't handle the case where the current prev.fullName is no longer present in
allRepos, so the UI keeps a stale selection; update the setter logic so that if
there is no ?repo= (urlRepo falsy) and prev.fullName is non-empty but cannot be
found in allRepos, you promote a valid fallback (e.g., allRepos[0]) instead of
returning prev; ensure the existing behavior for URL-specified repo remains (set
selectedFromUrlRef.current = true when found) and keep the branch that promotes
the first live repo when prev was the placeholder.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b814b0ec-0f76-4f3d-9dd3-4d5cd3fd248e

📥 Commits

Reviewing files that changed from the base of the PR and between e07dba8 and 38f458a.

📒 Files selected for processing (19)
  • src/app/api/all-repos/route.ts
  • src/app/api/issues/route.ts
  • src/app/api/poller-status/route.ts
  • src/app/api/pulls/route.ts
  • src/app/api/repo-activity/route.ts
  • src/app/api/repos/[owner]/[name]/issues/route.ts
  • src/app/api/repos/[owner]/[name]/pulls/route.ts
  • src/app/docs/page.tsx
  • src/app/issues/page.tsx
  • src/app/pulls/page.tsx
  • src/components/IssuesTable.tsx
  • src/components/NewIssuesWatcher.tsx
  • src/components/RepoExplorer.tsx
  • src/lib/api-utils.ts
  • src/lib/db.ts
  • src/lib/poller.ts
  • src/lib/refresh.ts
  • src/lib/repos-server.ts
  • src/lib/use-sn74-repos.ts
💤 Files with no reviewable changes (1)
  • src/lib/api-utils.ts

Comment thread src/lib/refresh.ts
Comment thread src/lib/repos-server.ts
@MkDev11 MkDev11 changed the title Fix/defer pr issue links backfill fix: defer pr issue links backfill May 27, 2026
@MkDev11 MkDev11 merged commit b8a55fc into main May 27, 2026
3 of 4 checks passed
@MkDev11 MkDev11 deleted the fix/defer-pr-issue-links-backfill branch May 27, 2026 02:47
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.

1 participant