fix(codex): cache plugin inventory to prevent repeated disk I/O (#99071)#99177
fix(codex): cache plugin inventory to prevent repeated disk I/O (#99071)#99177jtcole wants to merge 1 commit into
Conversation
During complex multi-turn tasks, buildCodexPluginThreadConfig calls readCodexPluginInventory up to 4 times, and each call issues a plugin/list RPC to the Codex app-server. The app-server then scans ~180 plugin.json files from disk on every call, producing 1.3–1.4 GB of disk reads in 30 seconds and driving disk utilization to 99% on resource-constrained cloud servers. The existing CodexAppInventoryCache only caches app/list responses. The plugin/list and plugin/read RPC paths had no cache at all. This commit introduces CodexPluginListCache — a process-local in-memory cache for plugin/list responses with a 5-minute TTL and coalesced refreshes. The cache is wired into: - readCodexPluginInventory (plugin-inventory.ts): plugin/list calls now use readOrRefresh() which returns a cached fresh snapshot instead of hitting the server - buildCodexPluginThreadConfig (plugin-thread-config.ts): all 4 readCodexPluginInventory calls now pass the shared plugin list cache - attempt-startup.ts: the build callback passes defaultCodexPluginListCache alongside the existing app inventory cache - plugin-activation.ts: the cache is invalidated on plugin install and during refreshCodexPluginRuntimeState so post-install state changes are always observed The activation path (ensureCodexPluginActivation) intentionally does NOT use the cache for its initial plugin/list read — it needs fresh install/enable state to decide whether plugin/install is needed. Tests: - 10 new tests in plugin-list-cache.test.ts covering cache reads, refreshes, coalescing, invalidation, forced refetch, error propagation, and revision tracking - 2 new tests in plugin-inventory.test.ts verifying that repeated readCodexPluginInventory calls use the cached plugin/list response and that forcePluginListRefetch bypasses the cache - plugin-thread-config.test.ts clears defaultCodexPluginListCache in beforeEach to prevent cross-test cache pollution Fixes #99071
|
Codex review: needs real behavior proof before merge. Reviewed July 2, 2026, 1:13 PM ET / 17:13 UTC. Summary PR surface: Source +258, Tests +335. Total +593 across 8 files. Reproducibility: no. high-confidence live reproduction was run for the full disk-I/O symptom on current main. Source inspection does confirm the reported repeated OpenClaw Review metrics: 1 noteworthy metric.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Fix the unused-symbol errors, keep the bounded cache only with an accepted freshness/invalidation story, and add redacted live Gateway/Codex profiling proof before merge. Do we have a high-confidence way to reproduce the issue? No high-confidence live reproduction was run for the full disk-I/O symptom on current main. Source inspection does confirm the reported repeated OpenClaw Is this the best way to solve the issue? No, not as submitted: the cache is a plausible bounded fix for repeated OpenClaw Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against d3d7282a8cf4. Label changesLabel justifications:
Evidence reviewedPR surface: Source +258, Tests +335. Total +593 across 8 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
This pull request has been automatically marked as stale due to inactivity. |
|
ClawSweeper status: review started. I am starting a fresh review of this pull request: fix(codex): cache plugin inventory to prevent repeated disk I/O (#99071) This is item 1/1 in the current shard. Shard 1/22. This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking. Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted. |
Problem
During complex multi-turn tasks,
buildCodexPluginThreadConfigcallsreadCodexPluginInventoryup to 4 times per request cycle. Each call issues aplugin/listRPC to the Codex app-server, which scans ~180plugin.jsonfiles from disk. This produces 1.3–1.4 GB of disk reads in 30 seconds and drives disk utilization to 99% on resource-constrained cloud servers (2 vCPU / 4 GB RAM Ubuntu 24.04.4), making the server unresponsive.Reported in #99071.
Root Cause
The existing
CodexAppInventoryCacheonly cachesapp/listresponses. Theplugin/listandplugin/readRPC paths — the ones that trigger the heavy disk scanning — had no cache at all. Every call toreadCodexPluginInventoryresulted in a freshplugin/listRPC, andbuildCodexPluginThreadConfigcalls it up to 4 times:Additionally,
ensureCodexPluginActivationinplugin-activation.tsmakes its ownplugin/listcall for each plugin that needs activation.Fix
Introduces
CodexPluginListCache— a process-local in-memory cache forplugin/listresponses with:Wiring
plugin-list-cache.tsCodexPluginListCacheclass +defaultCodexPluginListCachesingletonplugin-inventory.tsreadCodexPluginInventoryusesreadOrRefresh()forplugin/listwhen cache is providedplugin-thread-config.tsreadCodexPluginInventorycalls +ensureCodexPluginActivationpass the shared cacheattempt-startup.tsbuildCodexPluginThreadConfigcallback passesdefaultCodexPluginListCacheplugin-activation.tsrefreshCodexPluginRuntimeStateDesign Decision: Activation Path Stays Uncached
ensureCodexPluginActivationintentionally does NOT use the cache for its initialplugin/listread. It needs fresh install/enable state to decide whetherplugin/installis needed. Using a cached response could show a plugin as not-yet-installed when it actually was just installed by a concurrent process, leading to a redundantplugin/installcall. The cache is invalidated after install so subsequent reads see the new state.Impact
Before: 4+
plugin/listRPCs perbuildCodexPluginThreadConfigcall × ~180plugin.jsonfile reads each = 1.3–1.4 GB disk I/O in 30 seconds.After: 1
plugin/listRPC per cache TTL window (5 minutes). Subsequent calls within that window return the cached snapshot. Cache is invalidated on plugin install/activation so post-install state changes are always observed.Tests
plugin-list-cache.test.ts: cache reads, refreshes, coalescing, invalidation, forced refetch, error propagation, revision tracking, clearplugin-inventory.test.ts: verify repeatedreadCodexPluginInventorycalls use the cachedplugin/listresponse, andforcePluginListRefetchbypasses the cacheplugin-thread-config.test.ts:beforeEachclearsdefaultCodexPluginListCacheto prevent cross-test cache pollutionFixes #99071