Skip to content

fix(gateway): memoize plugin descriptor config keys#76240

Merged
joshavant merged 1 commit into
mainfrom
fix/plugin-tool-descriptor-config-cache
May 2, 2026
Merged

fix(gateway): memoize plugin descriptor config keys#76240
joshavant merged 1 commit into
mainfrom
fix/plugin-tool-descriptor-config-cache

Conversation

@joshavant

Copy link
Copy Markdown
Contributor

Summary

  • Problem: cloned runtime configs can miss the runtime snapshot identity fast path and force repeated full stable-stringify/SHA256 cache-key work while resolving plugin tool descriptors.
  • Why it matters: in large Windows gateway configs, this blocks the event loop during reply startup and matches the core-plugin-tools stall pattern reported in [Bug]: After enabling the gateway, it keeps timing out and reconnecting repeatedly #75944.
  • What changed: add a per-resolvePluginTools WeakMap memo for descriptor config cache keys and thread it through cached descriptor reads/writes.
  • What did NOT change (scope boundary): no Feishu/Lark, MiniMax, WebSocket, provider routing, secrets snapshot cloning, or skill discovery behavior changed.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: plugin descriptor cache keys called resolveRuntimeConfigCacheKey repeatedly for the same cloned config object. Clones returned from the active secrets runtime are semantically equivalent to the active runtime snapshot, but they do not satisfy the object-identity fast path in resolveRuntimeConfigCacheKey, so each descriptor key could re-stringify and hash the whole config.
  • Missing detection / guardrail: descriptor cache-key tests covered correctness but not repeated expensive config-key resolution for the same object inside one tool-resolution pass.
  • Contributing context: [Bug]: After enabling the gateway, it keeps timing out and reconnecting repeatedly #75944 logs show large core-plugin-tools stages and event-loop/cpu saturation on Windows. This patch targets that concrete hot path, not every later stage in the reporter's trace.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/plugins/tool-descriptor-cache.test.ts
  • Scenario the test should lock in: repeated descriptor cache keys for the same config object in one resolution pass resolve the expensive config cache key once, while distinct config objects remain distinct.
  • Why this is the smallest reliable guardrail: it directly covers the hot helper that multiplies config hashing across plugin descriptors without needing live Feishu/MiniMax credentials.
  • Existing test that already covers this (if any): none.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

Large gateway configs should spend far less synchronous CPU time during plugin tool descriptor setup, reducing reply-startup event-loop stalls and downstream WebSocket timeouts/reconnects for this path.

Diagram (if applicable)

Before:
resolvePluginTools -> descriptor key per plugin -> hash same cloned config repeatedly -> event loop blocked

After:
resolvePluginTools -> descriptor key per plugin -> reuse per-pass config key memo -> descriptor setup stays responsive

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation: N/A

Repro + Verification

Environment

  • OS: macOS local development host; Windows 11 Parallels guest for live Windows proof
  • Runtime/container: Node 24.14.1 locally; Node 24.15.0 in Windows guest
  • Model/provider: N/A for focused hot-path repro
  • Integration/channel (if any): N/A for focused hot-path repro
  • Relevant config (redacted): synthetic large runtime config with plugin entries, matching the cloned-runtime-config cache-key shape from [Bug]: After enabling the gateway, it keeps timing out and reconnecting repeatedly #75944

Steps

  1. Build a large runtime config and set it as the runtime snapshot.
  2. Pass a structured clone of that config through plugin descriptor cache-key construction.
  3. Compare current-main behavior without memoization against the patched per-resolution memo path.

Expected

  • Same-object descriptor config keys should be resolved once per tool-resolution pass.
  • Event-loop delay should drop sharply for cloned runtime configs.

Actual

  • Current-main behavior repeatedly hashes the cloned config for every descriptor key config slot.
  • Patched behavior reuses the memoized config key.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Focused tests:

pnpm test src/plugins/tool-descriptor-cache.test.ts src/plugins/tools.optional.test.ts
# 2 files passed, 38 tests passed

Formatting:

pnpm exec oxfmt --check --threads=1 CHANGELOG.md src/plugins/tool-descriptor-cache.ts src/plugins/tools.ts src/plugins/tool-descriptor-cache.test.ts
# All matched files use the correct format.

macOS deterministic probe:

{"useMemo":false,"keys":50,"entries":10000,"workMs":4089.8,"timerDelayMs":4090.5}
{"useMemo":true,"keys":50,"entries":10000,"workMs":31.4,"timerDelayMs":34.4}

Windows 11 Parallels live before/after hot-path proof:

{"node":"v24.15.0","platform":"win32","arch":"arm64"}
{"label":"clone/current-main","useMemo":false,"entries":2000,"keys":50,"workMs":3441.6,"timerDelayMs":3442.9,"hashCalls":150}
{"label":"clone/patched-memo","useMemo":true,"entries":2000,"keys":50,"workMs":58.5,"timerDelayMs":58.7,"hashCalls":1}

Changed gate:

pnpm check:changed
# Passed in Blacksmith Testbox for lanes: core, coreTests, docs

Human Verification (required)

  • Verified scenarios: focused plugin descriptor config-key memoization test; existing plugin optional-tool descriptor cache tests; macOS deterministic before/after probe; Windows 11 Parallels live before/after hot-path probe; Testbox changed gate.
  • Edge cases checked: distinct config objects still produce distinct descriptor keys inside the same memo.
  • What you did not verify: full end-to-end Feishu/Lark persistent connection plus MiniMax-M2.7 validation from [Bug]: After enabling the gateway, it keeps timing out and reconnecting repeatedly #75944. This PR validates the concrete core-plugin-tools cloned-config hashing hot path, not every symptom in the reporter's environment.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: caching config keys too broadly could make descriptor keys stale if callers mutate config objects.
    • Mitigation: the memo is scoped to one resolvePluginTools pass, not global descriptor cache state.
  • Risk: object-identity memoization could merge distinct configs accidentally.
    • Mitigation: the WeakMap is keyed by object identity; tests assert distinct objects remain distinct.

@openclaw-barnacle openclaw-barnacle Bot added size: S maintainer Maintainer-authored PR labels May 2, 2026
@clawsweeper

clawsweeper Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Summary
The PR adds a per-resolvePluginTools WeakMap memo for plugin descriptor config cache keys, threads it through descriptor cache reads/writes, adds focused regression coverage, and updates the changelog.

Reproducibility: yes. The related issue provides a high-confidence report-level reproduction with Windows, Feishu/Lark persistent connection, MiniMax, and core-plugin-tools stall logs, and this PR adds a focused synthetic cache-key reproduction path.

Next step before merge
This is an open implementation PR with no discrete automated repair finding; maintainer review and exact-head check gating are the remaining actions.

Security
Cleared: The diff only changes in-process memoization, tests, and changelog text; it does not add dependencies, workflows, permissions, secrets handling, network calls, or command execution paths.

Review details

Best possible solution:

Land this per-resolution descriptor config-key memo with the added regression coverage once exact-head checks and maintainer review are clean.

Do we have a high-confidence way to reproduce the issue?

Yes. The related issue provides a high-confidence report-level reproduction with Windows, Feishu/Lark persistent connection, MiniMax, and core-plugin-tools stall logs, and this PR adds a focused synthetic cache-key reproduction path.

Is this the best way to solve the issue?

Yes. A WeakMap scoped to one resolvePluginTools pass is the narrowest maintainable fix for repeated same-object config hashing because it avoids global stale cache state while preserving distinct object identities.

What I checked:

  • Current main hot path has no per-pass memo: buildDescriptorContextCacheKey calls getDescriptorConfigCacheKey separately for ctx.config, ctx.runtimeConfig, and currentRuntimeConfig; each non-null object calls resolveRuntimeConfigCacheKey directly on current main. (src/plugins/tool-descriptor-cache.ts:50, 10448a0ad14c)
  • Runtime config cache key fallback is expensive for cloned configs: resolveRuntimeConfigCacheKey only uses the cheap runtime snapshot key when the object is exactly the active snapshot; cloned equivalent configs fall through to hashing the config value. (src/config/runtime-snapshot.ts:164, 10448a0ad14c)
  • PR threads memo through descriptor key reads and writes: The diff adds createPluginToolDescriptorConfigCacheKeyMemo, passes configCacheKeyMemo into descriptor key construction, and creates one memo per resolvePluginTools call. (src/plugins/tools.ts:720, 28bc96ca2269)
  • Regression coverage targets same-object reuse and distinct-object separation: The added test asserts repeated descriptor keys for the same config object call resolveRuntimeConfigCacheKey once, while distinct config objects still produce distinct keys. (src/plugins/tool-descriptor-cache.test.ts:27, 28bc96ca2269)
  • Related issue has concrete reproduction logs: The linked issue discussion includes Windows 10/11, Node 24, OpenClaw 2026.4.29, Feishu/Lark persistent connection, MiniMax-M2.7, and traces where core-plugin-tools takes roughly 99-115 seconds.
  • Live PR state is open and protected-label gated: The PR API reports state: open, mergeable: true, mergeable_state: unstable, labels maintainer and size: S, and head SHA 28bc96ca22697f1dfbff8abd652e80784639c601. (28bc96ca2269)

Likely related people:

  • steipete: GitHub commit history shows this handle authored the plugin descriptor cache introduction and the runtime config revision/cache-key path that makes cloned configs fall back to hashing. (role: introduced behavior and recent maintainer; confidence: high; commits: 1466878c36ff, 3240cccb8ae3, 94a9d3f0bedb; files: src/plugins/tool-descriptor-cache.ts, src/plugins/tools.ts, src/config/runtime-snapshot.ts)
  • shakkernerd: Recent merged plugin tool manifest-contract and discovery-scope work touched the same resolvePluginTools ownership surface, and the descriptor-cache introduction credits this handle as co-author. (role: adjacent plugin-tool maintainer; confidence: medium; commits: e6825fceaa04, 7641783d6b0e, 3cf1dd982ba0; files: src/plugins/tools.ts, src/plugins/tool-descriptor-cache.ts)
  • DmitryPogodaev: Recent plugin runtime performance work reused startup runtime registries across provider/tool helper paths near this descriptor cache surface. (role: adjacent performance owner; confidence: medium; commits: 8283c5d6cc3f; files: src/plugins/tools.ts)

Remaining risk / open question:

  • Exact-head CI was not fully complete when sampled: 4 check runs were still in progress among the first 100 check runs.
  • The PR validates the focused cloned-config descriptor hashing hot path, not the full Feishu/Lark plus MiniMax end-to-end freeze from the related issue.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 10448a0ad14c.

@joshavant
joshavant force-pushed the fix/plugin-tool-descriptor-config-cache branch from 8edc048 to ba0123e Compare May 2, 2026 20:16
@joshavant
joshavant force-pushed the fix/plugin-tool-descriptor-config-cache branch from ba0123e to 28bc96c Compare May 2, 2026 20:19
@joshavant
joshavant merged commit b779c45 into main May 2, 2026
102 checks passed
@joshavant
joshavant deleted the fix/plugin-tool-descriptor-config-cache branch May 2, 2026 20:25
vincentkoc added a commit that referenced this pull request May 2, 2026
* 'main' of https://github.com/openclaw/openclaw:
  Wizard: bound hatch TUI timeout (#76241)
  fix(cli): reject codex simple-completion probes
  fix: memoize plugin descriptor config keys (#76240)
  fix(heartbeat): make phase scheduling active-hours-aware (#75487) (#75597)
lxe pushed a commit to lxe/openclaw that referenced this pull request May 6, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
* 'main' of https://github.com/openclaw/openclaw:
  Wizard: bound hatch TUI timeout (openclaw#76241)
  fix(cli): reject codex simple-completion probes
  fix: memoize plugin descriptor config keys (openclaw#76240)
  fix(heartbeat): make phase scheduling active-hours-aware (openclaw#75487) (openclaw#75597)
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
* 'main' of https://github.com/openclaw/openclaw:
  Wizard: bound hatch TUI timeout (openclaw#76241)
  fix(cli): reject codex simple-completion probes
  fix: memoize plugin descriptor config keys (openclaw#76240)
  fix(heartbeat): make phase scheduling active-hours-aware (openclaw#75487) (openclaw#75597)
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
* 'main' of https://github.com/openclaw/openclaw:
  Wizard: bound hatch TUI timeout (openclaw#76241)
  fix(cli): reject codex simple-completion probes
  fix: memoize plugin descriptor config keys (openclaw#76240)
  fix(heartbeat): make phase scheduling active-hours-aware (openclaw#75487) (openclaw#75597)
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
* 'main' of https://github.com/openclaw/openclaw:
  Wizard: bound hatch TUI timeout (openclaw#76241)
  fix(cli): reject codex simple-completion probes
  fix: memoize plugin descriptor config keys (openclaw#76240)
  fix(heartbeat): make phase scheduling active-hours-aware (openclaw#75487) (openclaw#75597)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant