Skip to content

fix(plugins): keep plugin metadata memo fresh#81064

Merged
steipete merged 10 commits into
openclaw:mainfrom
Kaspre:perf/plugin-metadata-snapshot-process-memo
May 15, 2026
Merged

fix(plugins): keep plugin metadata memo fresh#81064
steipete merged 10 commits into
openclaw:mainfrom
Kaspre:perf/plugin-metadata-snapshot-process-memo

Conversation

@Kaspre

@Kaspre Kaspre commented May 12, 2026

Copy link
Copy Markdown
Contributor

What this PR does

When loadPluginMetadataSnapshot is 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.json between 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:

  1. Plugin developers editing their own openclaw.plugin.json and re-running within the same OC session.
  2. Managed npm packages that materialize after the first snapshot (e.g., a late install completes between calls).
  3. Env-scoped state changes (HOME, OPENCLAW_*) where the cache shouldn't be reused across env contexts at all.

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

  • 2 files changed: src/plugins/plugin-metadata-snapshot.ts (logic) + src/plugins/plugin-metadata-snapshot.memo.test.ts (regression coverage).
  • The watched-file fingerprint is computed once when the cache misses, then reused on every subsequent same-process call until one of the watched files actually changes. The fast registry fingerprint runs first so unchanged-state lookups stay cheap.

Real behavior proof

Behavior or issue addressed: after #81570, loadPluginMetadataSnapshot could 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 on origin/main c8228245039b777004de37d59eb69527fb060af7.

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 only openclaw.plugin.json while leaving plugins/installs.json unchanged, 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:check and diff --check also 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 mutation
  • pnpm -C <repo> test src/plugins/plugin-metadata-snapshot.memo.test.ts — 1 file, 20 tests passed
  • pnpm -C <repo> format:check src/plugins/plugin-metadata-snapshot.ts src/plugins/plugin-metadata-snapshot.memo.test.ts
  • git -C <repo> diff --check
  • Remote pnpm install + build:plugin-sdk:dts + tsgo:prod + check:test-types + memo test validation on GCP e2-standard-8 (Crabbox)

@openclaw-barnacle openclaw-barnacle Bot added size: XL triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 12, 2026
@clawsweeper

clawsweeper Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge.

Summary
The PR widens the process-local plugin metadata snapshot memo fingerprint so unchanged registry-index cache hits still invalidate when watched plugin metadata files, install records, or env-scoped inputs change.

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
Needs stronger real behavior proof before merge: The PR body contains useful live loader output, but it was run at 2b80543 while current head is 43d88fd after later implementation commits; add refreshed current-head live output, logs, or a terminal screenshot with private details redacted, then update the PR body or ask a maintainer to comment @clawsweeper re-review if it does not rerun automatically.

Next step before merge
No automated repair is indicated; the remaining blocker is current-head proof or maintainer override plus normal required-check completion.

Security
Cleared: No concrete security or supply-chain concern found; the diff adds local filesystem metadata fingerprinting and tests without new dependencies, workflow permission changes, secret handling, or plugin execution.

Review details

Best 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:

  • Refresh real behavior proof against current head 43d88fd, ideally the same live loader alias-refresh harness or equivalent redacted terminal output.
  • Wait for required current-head checks, including check-test-types, to complete successfully.

What I checked:

  • Current main memo gap: Current main builds the memo key from control-plane inputs plus persisted registry and managed npm root package signatures, and the in-code comment says direct plugin file edits are outside the current memo freshness boundary. (src/plugins/plugin-metadata-snapshot.ts:137, 59d2e7686c6e)
  • Plugin metadata contract: The scoped plugin guide says metadata should stay fresh unless a caller owns an explicit metadata snapshot, lookup table, or manifest registry for the current flow. (src/plugins/AGENTS.md:30, 59d2e7686c6e)
  • PR implementation surface: At current PR head, lookup computes a fast registry hash, reuses stored watched-file hashes on hot hits, and recomputes a widened registry fingerprint over persisted plugin files, install records, and managed npm metadata when needed. (src/plugins/plugin-metadata-snapshot.ts:383, 43d88fdf249d)
  • PR cache integration: At current PR head, loadPluginMetadataSnapshot stores the computed registry state with the memoized snapshot and recomputes the memo key with that cached state before returning hits. (src/plugins/plugin-metadata-snapshot.ts:699, 43d88fdf249d)
  • Regression coverage: The PR adds memo tests for policy-stale derived file changes, hot cache reuse, persisted manifest/source/setup/package changes, home-relative install records, managed npm recovery, symlink behavior, and outside-root protections. (src/plugins/plugin-metadata-snapshot.memo.test.ts:278, 43d88fdf249d)
  • Related PR scope: The closed performance hotfix explicitly left the broader same-process plugin metadata freshness matrix for this PR or a follow-up, so the remaining work is not superseded by that closed PR. (9de96674aff0)

Likely related people:

  • Kaspre: The current-main process-local metadata memo was introduced in f71df80, and the PR branch builds directly on that behavior. (role: introduced current memo behavior and follow-up author with main-history signal; confidence: high; commits: f71df8052291, de5227d6384c, 46836c1dcc2c; files: src/plugins/plugin-metadata-snapshot.ts, src/plugins/plugin-metadata-snapshot.memo.test.ts)
  • steipete: Recent plugin metadata snapshot/cache and installed-record recovery history is concentrated in this area, and the latest PR head commits were pushed by this person. (role: recent area contributor and current PR head updater; confidence: high; commits: 71da5af16499, 2294f5c95a6f, 4047f4d0b420; files: src/plugins/plugin-metadata-snapshot.ts, src/plugins/installed-plugin-index-record-reader.ts)
  • shakkernerd: History shows earlier plugin metadata snapshot extraction, metadata reuse fingerprinting, and installed-index record reader work on the same control-plane path. (role: original and adjacent plugin metadata contributor; confidence: medium; commits: 440fc73448ac, 94591c3cb3b4, 9e086d6ed833; files: src/plugins/plugin-metadata-snapshot.ts, src/plugins/manifest-registry-installed.ts, src/plugins/installed-plugin-index-record-reader.ts)
  • vincentkoc: The current-main memo commit was committed by vincentkoc, and recent manifest-registry metadata work touches adjacent freshness behavior. (role: recent adjacent plugin metadata contributor and committer signal; confidence: medium; commits: f71df8052291, c635f0087ec8; files: src/plugins/plugin-metadata-snapshot.ts, src/plugins/manifest-registry-installed.ts)

Remaining risk / open question:

  • The posted live proof predates current head 43d88fd, which includes later implementation changes in the same file; the PR needs refreshed current-head proof or maintainer override before merge.
  • At review time, check-test-types was still in progress for the current head.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 59d2e7686c6e.

@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 12, 2026
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. labels May 12, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 12, 2026
@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. labels May 12, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 12, 2026
@Kaspre Kaspre marked this pull request as draft May 14, 2026 02:20
@Kaspre Kaspre force-pushed the perf/plugin-metadata-snapshot-process-memo branch from 7e8f482 to 7acd775 Compare May 14, 2026 07:52
@openclaw-barnacle openclaw-barnacle Bot removed gateway Gateway runtime proof: sufficient ClawSweeper judged the real behavior proof convincing. labels May 14, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 14, 2026
@Kaspre Kaspre force-pushed the perf/plugin-metadata-snapshot-process-memo branch from 7acd775 to 3014103 Compare May 14, 2026 09:25
@openclaw-barnacle openclaw-barnacle Bot added size: M triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. size: L and removed size: XL proof: supplied External PR includes structured after-fix real behavior proof. proof: sufficient ClawSweeper judged the real behavior proof convincing. size: M labels May 14, 2026
@Kaspre Kaspre force-pushed the perf/plugin-metadata-snapshot-process-memo branch from 71a84af to 2b80543 Compare May 14, 2026 10:10
@Kaspre Kaspre changed the title perf(plugins): memoize metadata snapshots per process fix(plugins): keep plugin metadata memo fresh May 14, 2026
@Kaspre Kaspre marked this pull request as ready for review May 14, 2026 10:11
@openclaw-barnacle openclaw-barnacle Bot added proof: supplied External PR includes structured after-fix real behavior proof. and removed triage: mock-only-proof Candidate: PR proof only shows tests, mocks, snapshots, lint, typecheck, or CI. labels May 14, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 14, 2026
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 14, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 14, 2026
@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. labels May 14, 2026
@clawsweeper clawsweeper Bot added the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 14, 2026
@steipete steipete force-pushed the perf/plugin-metadata-snapshot-process-memo branch from 8301e76 to 43d88fd Compare May 15, 2026 05:56
@openclaw-barnacle openclaw-barnacle Bot removed the proof: sufficient ClawSweeper judged the real behavior proof convincing. label May 15, 2026
Kaspre and others added 10 commits May 15, 2026 07:04
`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.
@steipete steipete force-pushed the perf/plugin-metadata-snapshot-process-memo branch from 43d88fd to 74f48b9 Compare May 15, 2026 06:06
@openclaw-barnacle openclaw-barnacle Bot added the cli CLI command changes label May 15, 2026
@steipete

Copy link
Copy Markdown
Contributor

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:

  • node scripts/run-vitest.mjs src/plugins/plugin-metadata-snapshot.memo.test.ts
  • pnpm format:check src/plugins/plugin-metadata-snapshot.ts src/plugins/plugin-metadata-snapshot.memo.test.ts src/gateway/server.sessions.store-rpc.test.ts src/cli/update-cli.test.ts CHANGELOG.md
  • git diff --check
  • pnpm check:test-types
  • pnpm crabbox:run -- --provider blacksmith-testbox --blacksmith-org openclaw --blacksmith-workflow .github/workflows/ci-check-testbox.yml --blacksmith-job check --blacksmith-ref main --idle-timeout 90m --ttl 240m --timing-json --shell -- "env CI=1 NODE_OPTIONS=--max-old-space-size=4096 OPENCLAW_VITEST_MAX_WORKERS=1 OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=900000 bash -lc 'set -e; echo CRABBOX_PHASE:focused-test; node scripts/run-vitest.mjs src/plugins/plugin-metadata-snapshot.memo.test.ts; echo CRABBOX_PHASE:changed-check; pnpm check:changed'"
  • gh pr checks 81064 --repo openclaw/openclaw --watch --interval 30

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.

@steipete steipete merged commit 5734193 into openclaw:main May 15, 2026
115 of 116 checks passed
@Kaspre Kaspre deleted the perf/plugin-metadata-snapshot-process-memo branch May 15, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli CLI command changes gateway Gateway runtime proof: supplied External PR includes structured after-fix real behavior proof. size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants