Skip to content

fix: Prevent GitHub API rate limiting from frequent worktree PR fetching (fixes #685)#688

Merged
Shironex merged 3 commits intov0.14.0rcfrom
feature/bug-worktree-pr-fetching-still-fetch-too-frequent-26ay
Jan 24, 2026
Merged

fix: Prevent GitHub API rate limiting from frequent worktree PR fetching (fixes #685)#688
Shironex merged 3 commits intov0.14.0rcfrom
feature/bug-worktree-pr-fetching-still-fetch-too-frequent-26ay

Conversation

@Shironex
Copy link
Collaborator

@Shironex Shironex commented Jan 24, 2026

Summary

This PR fixes the GitHub API rate limiting issue caused by excessive worktree PR status fetching (#685).

Problem

The app was fetching GitHub PR status too frequently, causing rate limit errors:

  • Frontend was polling every 5 seconds
  • Server had no caching for PR data
  • Each poll triggered a gh pr list command
  • With many worktrees, this quickly exceeded GitHub's API limits

Solution

Server-side PR Caching (list.ts)

  • ✅ Added githubPRCache with 2-minute TTL
  • ✅ Cache is checked before making GitHub API calls
  • ✅ Force refresh option clears cache when explicitly requested
  • ✅ Proper error handling and logging maintained

Frontend Polling Reduction (worktree-panel.tsx)

  • ✅ Increased polling interval from 5s → 30s (6x reduction)
  • ✅ Balances UI responsiveness with API conservation

Type Improvements (use-worktrees.ts)

  • ✅ Fixed fetchWorktrees signature to properly handle silent option
  • ✅ Returns removed worktrees array for compatibility

Impact

  • ~12x reduction in GitHub API calls
  • 2-minute cache prevents hammering API during normal operation
  • 30-second polling keeps UI fresh while respecting rate limits
  • Force refresh option available when users need latest data

Testing

  • Verified cache works correctly with TTL expiration
  • Confirmed force refresh bypasses cache as expected
  • Tested with multiple worktrees to ensure no rate limiting
  • Polling interval update reduces API call frequency

Closes

Closes #685

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Performance Improvements

    • Short-term caching for GitHub PR data to speed loading and reduce external requests.
    • Reduced polling frequency for branch-change detection from 5s to 30s to lower resource use.
  • Developer-facing API

    • Worktree refresh can run silently and now returns removed worktrees when refetching.

✏️ Tip: You can customize this high-level summary in your review settings.

Fixes #685

This commit addresses the GitHub API rate limit issue caused by excessive worktree PR status fetching.

## Changes

### Server-side PR caching (list.ts)
- Added `GitHubPRCacheEntry` interface and `githubPRCache` Map
- Implemented 2-minute TTL cache for GitHub PR data
- Modified `fetchGitHubPRs()` to check cache before making API calls
- Added `forceRefresh` parameter to bypass cache when explicitly requested
- Cache is properly cleared when force refresh is triggered

### Frontend polling reduction (worktree-panel.tsx)
- Increased worktree polling interval from 5 seconds to 30 seconds
- Reduces polling frequency by 6x while keeping UI reasonably fresh
- Updated comment to reflect new polling strategy

### Type improvements (use-worktrees.ts)
- Fixed `fetchWorktrees` callback signature to accept `silent` option
- Returns proper type for removed worktrees detection

## Impact
- Combined ~12x reduction in GitHub API calls
- 2-minute cache prevents repeated API hits during normal operation
- 30-second polling balances responsiveness with API conservation
- Force refresh option allows users to manually update when needed

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @Shironex, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses and resolves the issue of GitHub API rate limiting by implementing a comprehensive strategy to reduce the frequency of API calls. It introduces server-side caching for GitHub PR data and significantly extends the frontend polling interval. These changes collectively lead to a substantial reduction in API requests, ensuring the application operates within GitHub's rate limits while maintaining a responsive user experience.

Highlights

  • Server-side PR Caching: Implemented a 2-minute Time-To-Live (TTL) cache for GitHub PR data on the server to significantly reduce API calls. The cache is checked before making new GitHub API requests, and a force refresh option is available to bypass it.
  • Frontend Polling Reduction: The frontend polling interval for worktree PR status has been increased from 5 seconds to 30 seconds, leading to a 6x reduction in frequent API requests while maintaining UI responsiveness.
  • Type Improvements for fetchWorktrees: The fetchWorktrees function signature was updated to correctly handle a silent option and now returns an array of removed worktrees for better compatibility and error handling.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Jan 24, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds a per-project GitHub PR cache with TTL and a force-refresh option on the server, updates the UI worktrees hook to return removed worktrees and accept a silent option, and increases the UI polling interval from 5s to 30s.

Changes

Cohort / File(s) Summary
Server: PR caching & fetch logic
apps/server/src/routes/worktree/routes/list.ts
Adds GitHubPRCacheEntry and githubPRCache map with TTL (2 minutes). Extends fetchGitHubPRs(projectPath: string, forceRefresh = false) to use cache, store fresh results with timestamp, return stale cache on fetch failure when available, and accept forceRefresh to invalidate.
UI: worktrees hook API
apps/ui/src/components/views/board-view/worktree-panel/hooks/use-worktrees.ts
Changes fetchWorktrees() signature to fetchWorktrees(_options?: { silent?: boolean; }) and makes it return removedWorktrees (`Array<{ path: string; branch: string }>
UI: polling cadence
apps/ui/src/components/views/board-view/worktree-panel/worktree-panel.tsx
Increases polling interval for disk branch change detection from 5000 ms to 30000 ms and updates related comments.

Sequence Diagram(s)

sequenceDiagram
  participant UI as "UI / Client"
  participant Server as "Server (worktree list)"
  participant Cache as "Server Cache (githubPRCache)"
  participant GH as "GitHub / gh CLI"

  UI->>Server: Request worktree list (includeDetails, forceRefreshGitHub?)
  alt includeDetails requested
    Server->>Cache: Check githubPRCache[projectPath]
    alt cache valid && !forceRefresh
      Cache-->>Server: Return cached PRs
    else
      Server->>GH: Fetch PRs (gh pr list / GraphQL)
      alt GH returns PRs
        GH-->>Server: PRs
        Server->>Cache: Store PRs + fetchedAt
      else GH fails
        alt Cache has stale entry
          Cache-->>Server: Return stale PRs
        else
          Server-->>UI: Return empty PRs (warn)
        end
      end
    end
  end
  Server-->>UI: Worktree list (with PR info)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested labels

Bug, Ready-To-Merge, scope: ui

Poem

🐰 I stash PRs beneath the lea,
Two minutes fresh, then let them be.
Silent hops bring back what's torn,
Slower ticks save nerves well-worn.
I thump for quiet, code reborn.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: preventing GitHub API rate limiting by reducing frequent PR fetching through caching and reduced polling intervals.
Linked Issues check ✅ Passed The pull request implements all coding requirements from issue #685: server-side PR caching with TTL, reduced polling interval, graceful failure handling, and per-repo batching instead of per-worktree calls.
Out of Scope Changes check ✅ Passed All changes are within scope: PR caching mechanism, polling interval adjustment, and type signature fixes directly address the rate limiting issue and its requirements.

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

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses the GitHub API rate-limiting issue by introducing server-side caching for PR data and reducing the frontend polling frequency. The changes are well-structured and directly tackle the root causes of the problem. The type improvements in the use-worktrees hook are also a welcome addition for maintainability. I have one suggestion regarding the cache invalidation logic to make it more resilient to API flakiness, but overall this is a solid fix.

…d API calls

Address PR #688 review feedback: previously the cache was deleted before
fetch, causing repeated API calls if the fetch failed. Now the cache entry
is preserved and stale data is returned on failure, preventing unnecessary
API calls during GitHub API flakiness or temporary outages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@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: 1

🤖 Fix all issues with AI agents
In `@apps/server/src/routes/worktree/routes/list.ts`:
- Around line 248-259: When a fetch fails and you return stale cached PRs,
update the cache's fetchedAt to postpone TTL and avoid immediate retries; in the
catch block where you check cached, set or update githubPRCache for projectPath
so fetchedAt becomes Date.now() (or apply a simple backoff window) while keeping
prs as cached.prs, then return cached.prs (use symbols githubPRCache,
projectPath, cached, fetchedAt, getErrorMessage to locate the change).
🧹 Nitpick comments (1)
apps/server/src/routes/worktree/routes/list.ts (1)

190-204: Consider moving PR cache/fetch logic into a service layer.

This adds more business logic to a route handler; moving fetchGitHubPRs + cache to services/ would align with the server architecture and keep routes thin. As per coding guidelines, consider delegating this logic from routes/ to services/.

Address PR #688 review feedback from CodeRabbit: When a GitHub PR fetch
fails and we return stale cached data, also update the fetchedAt timestamp.
This prevents the original TTL from expiring and causing every subsequent
poll to retry the failing request, which would still hammer GitHub during
API outages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@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: 1

🤖 Fix all issues with AI agents
In `@apps/server/src/routes/worktree/routes/list.ts`:
- Around line 254-263: When a fetch for GitHub PRs fails and there is no
existing cached entry, add a short negative-cache entry to githubPRCache for the
given projectPath (e.g., store prs: new Map() or empty map and fetchedAt:
Date.now()) so subsequent polls use the negative cache instead of hammering
GitHub; update the error logging that uses getErrorMessage(error) as before, and
ensure the negative TTL is short (e.g., seconds-to-minutes) by relying on the
same cache expiry mechanism so normal polling resumes after the window; make
this change in the same failure branch where you currently log "Failed to fetch
GitHub PRs" so callers of the function that reads githubPRCache will see the
empty map until expiry.
🧹 Nitpick comments (1)
apps/server/src/routes/worktree/routes/list.ts (1)

193-203: Consider normalizing projectPath for the PR cache key.
If callers pass path variants (trailing slashes, symlinks), you’ll get duplicate cache entries and extra GitHub calls.

♻️ Suggested tweak
-  const cached = githubPRCache.get(projectPath);
+  const cacheKey = normalizePath(projectPath);
+  const cached = githubPRCache.get(cacheKey);
@@
-  if (!forceRefresh && cached && now - cached.fetchedAt < GITHUB_PR_CACHE_TTL_MS) {
+  if (!forceRefresh && cached && now - cached.fetchedAt < GITHUB_PR_CACHE_TTL_MS) {
     return cached.prs;
   }
@@
-    githubPRCache.set(projectPath, {
+    githubPRCache.set(cacheKey, {
       prs: prMap,
       fetchedAt: Date.now(),
     });
@@
-      githubPRCache.set(projectPath, { prs: cached.prs, fetchedAt: Date.now() });
+      githubPRCache.set(cacheKey, { prs: cached.prs, fetchedAt: Date.now() });
       return cached.prs;
     }

Comment on lines +254 to 263
// On fetch failure, return stale cached data if available to avoid
// repeated API calls during GitHub API flakiness or temporary outages
if (cached) {
logger.warn(`Failed to fetch GitHub PRs, returning stale cache: ${getErrorMessage(error)}`);
// Extend cache TTL to avoid repeated retries during outages
githubPRCache.set(projectPath, { prs: cached.prs, fetchedAt: Date.now() });
return cached.prs;
}
// No cache available, log warning and return empty map
logger.warn(`Failed to fetch GitHub PRs: ${getErrorMessage(error)}`);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid retry storms when there’s no cached PR data.
If the first fetch fails (rate limit/outage) and there’s no cache, every poll will still hit GitHub. Consider negative-caching an empty map for a short window to suppress repeated retries.

🛠️ Suggested fix
     if (cached) {
       logger.warn(`Failed to fetch GitHub PRs, returning stale cache: ${getErrorMessage(error)}`);
       // Extend cache TTL to avoid repeated retries during outages
       githubPRCache.set(projectPath, { prs: cached.prs, fetchedAt: Date.now() });
       return cached.prs;
     }
     // No cache available, log warning and return empty map
     logger.warn(`Failed to fetch GitHub PRs: ${getErrorMessage(error)}`);
+    const empty = new Map<string, WorktreePRInfo>();
+    githubPRCache.set(projectPath, { prs: empty, fetchedAt: Date.now() });
+    return empty;
🤖 Prompt for AI Agents
In `@apps/server/src/routes/worktree/routes/list.ts` around lines 254 - 263, When
a fetch for GitHub PRs fails and there is no existing cached entry, add a short
negative-cache entry to githubPRCache for the given projectPath (e.g., store
prs: new Map() or empty map and fetchedAt: Date.now()) so subsequent polls use
the negative cache instead of hammering GitHub; update the error logging that
uses getErrorMessage(error) as before, and ensure the negative TTL is short
(e.g., seconds-to-minutes) by relying on the same cache expiry mechanism so
normal polling resumes after the window; make this change in the same failure
branch where you currently log "Failed to fetch GitHub PRs" so callers of the
function that reads githubPRCache will see the empty map until expiry.

@Shironex Shironex merged commit 07f95ae into v0.14.0rc Jan 24, 2026
8 checks passed
@Shironex Shironex deleted the feature/bug-worktree-pr-fetching-still-fetch-too-frequent-26ay branch January 24, 2026 21:45
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