perf(store): cache GitHub API calls for faster plugin store loading - #251
Conversation
The plugin store was making excessive GitHub API calls causing slow page loads (10-30s): - Installed plugins endpoint called get_plugin_info() per plugin (3 GitHub API calls each) just to read the `verified` field from the registry. Use new get_registry_info() instead (zero API calls). - _get_latest_commit_info() had no cache — all 31 monorepo plugins share the same repo URL, causing 31 identical API calls. Add 5-min cache keyed by repo:branch. - _fetch_manifest_from_github() also uncached — add 5-min cache. - load_config() called inside loop per-plugin — hoist outside loop. - Install/update operations pass force_refresh=True to bypass caches and always get the latest commit SHA from GitHub. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR introduces caching for GitHub commit and manifest data in the plugin store manager, with optional Changes
Sequence DiagramsequenceDiagram
participant Client
participant StoreManager as Store Manager
participant Cache as Cache Layer
participant GitHub as GitHub API
Client->>StoreManager: install_plugin(id, force_refresh=True)
StoreManager->>Cache: Check manifest_cache
alt force_refresh=True
StoreManager->>GitHub: Fetch manifest
GitHub-->>StoreManager: manifest.json
StoreManager->>Cache: Update manifest_cache
else cache hit and valid
Cache-->>StoreManager: Cached manifest
end
StoreManager-->>Client: Installation result
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 🧹 Recent nitpick comments
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. Comment |
The heading read "B5 — adoption is safe by construction", which this same document disproves two sections later: four of the eight adopted plugins shipped with scroll mode broken on a 3.2.0 core and were repaired in plugins #251. The body was already careful -- it says fallback compatibility is what is guaranteed, and that correctness on a core which *does* ship the module needs object-level and scroll-mode validation. The heading was not, and a heading is what a reader scanning the plan actually takes away. Retitled to name both halves, with a sentence up front saying why the unqualified claim is false and pointing at the retrospective that shows it. The phase intro said "one of them is safe by construction and the other is not"; that now says what it actually means -- one cannot break a user on an old core, the other can. The second review point, MD018 on the ATX heading at line 409, does not reproduce: that line now begins "(#431, #433)" rather than "#433)", so there is no bare-hash heading. `grep -cE '^#+[^ #]'` returns 0 for the whole file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STMbQE4YctTacQXfbYqKuW
* docs(sports): record where B6 stands, and why it is waiting
The phase table had B4 as "next" and B5 as "after B4" while both had shipped,
and described B6 as blocked on B4's gate — which is now merged and released. A
plan that misreports which phase it is in is worse than no plan: the next
person reads it and repeats finished work.
Corrected, and three things that were only ever decided in conversation are now
written down:
* **B6 is deliberately held.** 3.2.0 published 2026-08-03; 3.1.0 ran nine
months before it. B6's premise is that cores without the module are gone,
and there is no release-asset count or install telemetry to show that.
Running it now strands users on their current plugin versions. The gate
that makes it safe is already built and tested — it is the calendar that is
missing, and no amount of further code changes that.
* **Stop adopting further shared modules** (data_sources, game_renderer,
base_odds_manager) until B6 closes. Each adoption adds a copy to keep in
step against a payoff contingent on B6.
* **A B5 retrospective**, because "the adoption went fine" is not what
happened: four of eight shipped with scroll mode broken on a 3.2.0 core.
The bundled fallback did not protect against it — the break was on the
modern path — which is an argument for the sunset, not against it. Records
the ledger too: net negative on disk until B6 runs.
Also replaces the "what's next" list, whose first five items were all done,
with what actually remains.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
* fix: apply CodeRabbit auto-fixes
Fixed 1 file(s) based on 2 unresolved review comments.
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
* docs(sports): stop a wrapped PR reference reading as a heading
A line wrapped onto "#433), the newest manifest entry ...", which
markdownlint reads as a malformed ATX heading (MD018). Reflowed so the
line starts with "(#431, #433)" instead.
Not the suggested fix: adding a space after the hash would have turned
the PR reference into "# 433". The B5 safety claim raised alongside this
was already corrected in ac44b5a.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
* docs(sports): scope the B5 safety claim to the fallback
The heading read "B5 — adoption is safe by construction", which this same
document disproves two sections later: four of the eight adopted plugins
shipped with scroll mode broken on a 3.2.0 core and were repaired in
plugins #251.
The body was already careful -- it says fallback compatibility is what is
guaranteed, and that correctness on a core which *does* ship the module
needs object-level and scroll-mode validation. The heading was not, and a
heading is what a reader scanning the plan actually takes away.
Retitled to name both halves, with a sentence up front saying why the
unqualified claim is false and pointing at the retrospective that shows
it. The phase intro said "one of them is safe by construction and the
other is not"; that now says what it actually means -- one cannot break a
user on an old core, the other can.
The second review point, MD018 on the ATX heading at line 409, does not
reproduce: that line now begins "(#431, #433)" rather than "#433)", so
there is no bare-hash heading. `grep -cE '^#+[^ #]'` returns 0 for the
whole file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STMbQE4YctTacQXfbYqKuW
* docs(sports): re-check the hold, and close two items that are already done
The remaining-work list had two entries that finished without the doc noticing,
which is the failure mode this file exists to prevent.
- The stale plugin-test tranche is gone. run_plugin_tests.py --all now reports
174 passed, 2 skipped, 0 failed across the whole fleet. Recorded how to
re-check it too: these are standalone scripts, not a pytest suite, and one
calls sys.exit(1) at import, so pointing pytest at a plugin directory
collapses into an INTERNALERROR that looks nothing like the real state.
- CLAUDE.md already says eight panel sizes.
That leaves the hardware soaks as the only open item needing work rather than
calendar time.
B6's prerequisite is now built -- core test/test_sports_sunset_matrix.py
(#505) -- so the phase table and the regression-test section say so, and the
two modelling traps it had to work through are recorded for whoever touches it
next: the copy-removed shape must be an unguarded import or the failure names
scroll_display_legacy instead of the core module, and only the leaf module may
be hidden because a pre-3.2.0 core still ships src/common/.
The hold itself is re-checked and unchanged: v3.2.0 is still latest,
__version__ is still 3.2.0, no 3.3.0, 23 days rather than the few months the
gate asks for. Also worth stating plainly -- the core updates by git pull, not
by downloading a release, so release-asset counts would not measure uptake even
if we had them. Whatever unblocks this has to come from the store side.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STMbQE4YctTacQXfbYqKuW
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Summary
The plugin store pages were extremely slow (10-30 seconds) due to excessive GitHub API calls:
get_plugin_info()for each plugin (3 GitHub API calls each) just to readverified— which is already in the cached registry. Now uses newget_registry_info()(zero API calls)_get_latest_commit_info(): No cache. All 31 monorepo plugins share the same repo URL, causing 31 identical API calls. Now cached for 5 minutes (keyed byrepo:branch)_fetch_manifest_from_github(): No cache. 31 calls per store load. Now cached for 5 minutesload_config(): Called inside the plugin loop (once per plugin) in bothapi_v3.pyandpages_v3.py. Hoisted outside the loopforce_refresh=Trueto bypass caches and always get the real latest commit SHA from GitHubExpected improvement
Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
Performance
New Features
Improvements