fix(plugins): keep plugin metadata memo fresh#81064
Conversation
|
Codex review: needs real behavior proof before merge. Summary Reproducibility: yes. source-reproducible: current main only keys the process memo on persisted registry and npm-root signatures while the plugin boundary expects metadata freshness. The PR body also shows a live alias-refresh proof, though that proof is not from the latest head. Real behavior proof Next step before merge Security Review detailsBest possible solution: Land the focused freshness hardening after current-head real behavior proof and required checks confirm the widened memo key preserves the hot path. Do we have a high-confidence way to reproduce the issue? Yes, source-reproducible: current main only keys the process memo on persisted registry and npm-root signatures while the plugin boundary expects metadata freshness. The PR body also shows a live alias-refresh proof, though that proof is not from the latest head. Is this the best way to solve the issue? Yes for the code direction: the patch keeps the process memo but ties it to the metadata files consumed by the persisted registry path and adds targeted regression coverage. The merge gate should wait for refreshed current-head proof rather than a code repair. Acceptance criteria:
What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 59d2e7686c6e. |
7e8f482 to
7acd775
Compare
7acd775 to
3014103
Compare
71a84af to
2b80543
Compare
8301e76 to
43d88fd
Compare
`resolvePersistedRegistryFastMemoFingerprint` was annotated `: unknown`
but always returns object literals (`{ disabled: true }` or
`{ index, npmPackageJson }`). Spreading the unknown-typed result on
line 478 (`...fastFingerprint`) was rejected by tsgo with TS2698, which
cascaded across every check that runs the project compile (build,
tsgo:prod, check:test-types, lint, all node test shards).
Tighten the return type to `Record<string, unknown>` to match the
function's actual return shapes and unblock the spread.
The `sessions.list configuredAgentsOnly hides disk-discovered unregistered agent stores` test spies on `fsSync.readFileSync` and predicates with `fsSync.realpathSync.native(file) === realDiskOnlyStorePath` for every captured read. The native realpath call throws on missing files, so any new readFileSync of a path that may not exist (e.g. the persisted plugin install records probe added in this PR) crashes the predicate before the assertion runs. Wrap the predicate in ENOENT tolerance so the test stays robust against any future readFileSync of files that may not exist on disk.
43d88fd to
74f48b9
Compare
|
Behavior addressed: process-local plugin metadata snapshot memo now keys freshness from the cached snapshot's actual registry inputs, including policy-stale derived indexes, so derived plugin manifest/root edits invalidate cached command aliases and owner metadata instead of returning stale metadata. Real environment tested: local macOS checkout; Blacksmith Testbox tbx_01krn3x2q2nvwzqgjqnxa445yh (jade-krill); GitHub CI on head 74f48b9. Exact steps or command run after this patch:
Evidence after fix: Testbox tbx_01krn3x2q2nvwzqgjqnxa445yh exited 0; focused regression passed 21/21; pnpm check:changed passed; timing JSON totalMs=199023 commandMs=196248 focused-test=56382 changed-check=91578. GitHub CI run https://github.com/openclaw/openclaw/actions/runs/25903194946 passed required check lanes, including check-test-types. Observed result after fix: no stale derived plugin metadata memo reuse after derived manifest/root changes; no type, format, lint, import-cycle, or changed-check failures on the touched surface. What was not tested: full cross-OS/manual plugin install E2E was not run; scope stayed on plugin metadata memo behavior and changed-file gates. |
What this PR does
When
loadPluginMetadataSnapshotis called more than once in the same OpenClaw process, an in-process cache (added in #81570) skips the expensive plugin discovery scan and reuses the previous snapshot. That cache currently keys on the registry index and a small set of environment inputs — so if a plugin's manifest, source files, or managed npm metadata change underneath without the registry index being touched, the second call can hand back a stale snapshot.This PR widens what the cache notices. It now fingerprints the underlying watched files (plugin manifests, source/setup/package metadata, recovered managed npm packages, and install records under the home-relative path) so any direct change to those files invalidates the cache and forces a fresh snapshot. The hot path is preserved: when nothing has changed, the cache still hits without re-scanning.
Practically: editing a plugin's
openclaw.plugin.jsonbetween two calls in the same process now produces an updated snapshot on the second call, instead of returning the cached pre-edit version.AI-assisted: yes, with Codex.
Why this matters
Same-process snapshot reuse is the common case (CLI commands, gateway request handling, agent dispatch within one node process). Without watched-file freshness, three classes of change can be silently masked:
openclaw.plugin.jsonand re-running within the same OC session.In each case, the registry index can stay byte-identical while the on-disk plugin metadata diverges from the cached snapshot.
Relationship to #81570
#81570 landed the perf hotfix for repeated in-process plugin metadata snapshots. This PR keeps that memo shape intact — it only widens the freshness key so persisted-registry cache hits also account for direct file changes. No reverts, no behavior change to the fast path when nothing has changed.
Scope
src/plugins/plugin-metadata-snapshot.ts(logic) +src/plugins/plugin-metadata-snapshot.memo.test.ts(regression coverage).Real behavior proof
Behavior or issue addressed: after #81570,
loadPluginMetadataSnapshotcould reuse a cached persisted snapshot in the same process even when persisted plugin metadata files, recovered managed npm package metadata, or env-scoped watched inputs changed underneath the accepted registry.Real environment tested: local source worktree on Linux/WSL2 at PR head
2b8054330fa1df464e0e2c42a1f4ee7934f64898, rebased onorigin/mainc8228245039b777004de37d59eb69527fb060af7.Exact steps or command run after this patch:
env NODE_NO_WARNINGS=1 node --import tsx /tmp/openclaw-pr-81064-live-proof.mjs;pnpm -C <repo> test src/plugins/plugin-metadata-snapshot.memo.test.ts;pnpm -C <repo> format:check src/plugins/plugin-metadata-snapshot.ts src/plugins/plugin-metadata-snapshot.memo.test.ts;git -C <repo> diff --check.Evidence after fix: the live proof harness created a temporary OpenClaw state directory, wrote a persisted plugin registry and real plugin manifest/package/source files, loaded
loadPluginMetadataSnapshot, changed onlyopenclaw.plugin.jsonwhile leavingplugins/installs.jsonunchanged, then called the real loader again. The second snapshot returned the changed command alias instead of the stale cached alias:{ "firstAlias": "proof-before", "secondAlias": "proof-after-metadata-change", "pluginCount": 1, "registryDiagnostics": [ "persisted-registry-stale-source" ] }The targeted memo test suite also passed: 1 file, 20 tests, covering persisted plugin manifests, source/setup/package metadata, home-relative install-record paths, recovered managed npm package metadata, late-created managed package files, package.json symlink targets, and HOME/env-specific watched sets without clearing the process memo.
format:checkanddiff --checkalso passed.Observed result after fix: #81064 remains a 2-file follow-up that preserves #81570's process memo performance shape and adds the missing same-process freshness guarantees for persisted plugin metadata inputs.
What was not tested: beta6 CLI startup timing was not rerun for this recut because that performance hotfix is already landed via #81570; this PR is freshness hardening only.
Verification
env NODE_NO_WARNINGS=1 node --import tsx /tmp/openclaw-pr-81064-live-proof.mjs— refreshed alias observed after unchanged-registry manifest mutationpnpm -C <repo> test src/plugins/plugin-metadata-snapshot.memo.test.ts— 1 file, 20 tests passedpnpm -C <repo> format:check src/plugins/plugin-metadata-snapshot.ts src/plugins/plugin-metadata-snapshot.memo.test.tsgit -C <repo> diff --checkpnpm install + build:plugin-sdk:dts + tsgo:prod + check:test-types + memo testvalidation on GCPe2-standard-8(Crabbox)