Skip to content

fix(plugins): mirror core root-package deps used by core dist code#74213

Merged
fabianwilliams merged 1 commit into
openclaw:mainfrom
maxpuppet:fix/runtime-deps-mirror-core-imports
Apr 29, 2026
Merged

fix(plugins): mirror core root-package deps used by core dist code#74213
fabianwilliams merged 1 commit into
openclaw:mainfrom
maxpuppet:fix/runtime-deps-mirror-core-imports

Conversation

@maxpuppet

Copy link
Copy Markdown

Summary

Extend `MIRRORED_CORE_RUNTIME_DEP_NAMES` (in `src/plugins/bundled-runtime-deps.ts`) to include the nine root-package runtime dependencies that core dist code imports but that the current allowlist does not mirror into the runtime-deps tree:

  • `@agentclientprotocol/sdk`
  • `croner`
  • `dotenv`
  • `jiti`
  • `json5`
  • `jszip`
  • `markdown-it`
  • `tar`
  • `web-push`

Context

Currently `MIRRORED_CORE_RUNTIME_DEP_NAMES` is just `["semver", "tslog"]`. The dynamic `collectRootDistMirroredRuntimeDeps` scan picks up other imports only when an enabled extension's `package.json` declares the dependency (so `chokidar` works in 2026.4.26 because `extensions/memory-core/package.json` declares it; `jiti` and `markdown-it` work when `matrix` is enabled because `extensions/matrix/package.json` declares them).

But these nine packages are imported by core dist code, not by an extension:

Package Imported by (representative)
`@agentclientprotocol/sdk` `src/acp/event-mapper.ts`, `src/acp/client-helpers.ts`
`croner` `src/cron/schedule.ts`
`dotenv` `src/config/state-dir-dotenv.ts`, `src/infra/dotenv.ts`
`jiti` `src/plugin-sdk/root-alias.cjs`, `src/plugins/jiti-loader-cache.ts`
`json5` `src/utils/parse-json-compat.ts`, `src/shared/frontmatter.ts`
`jszip` `src/infra/archive.ts`, `src/plugins/clawhub.ts`
`markdown-it` `src/markdown/ir.ts`
`tar` `src/commands/backup-verify.ts`
`web-push` `src/infra/push-web.ts`

For setups where no enabled extension owns the dependency, the imports never get installed into `~/.openclaw/plugin-runtime-deps/openclaw-/node_modules`, and Node fails to resolve them when the corresponding code path runs:

```
Cannot find package 'chokidar' imported from
~/.openclaw/plugin-runtime-deps/openclaw-2026.4.25-.../dist/qmd-manager-FuXCtSYP.js
```

(That specific case was fixed in 2026.4.26 because `memory-core` started declaring `chokidar`. The nine above behave the same way.)

Reproduction

Against a vanilla `npm i -g [email protected]`:

```bash
cd ~/.npm-global/lib/node_modules/openclaw/dist
grep -rh '^import .* from "[^./n]' *.js | grep -oE 'from "[^\"]+"' | sort -u

compare each top-level package against .specs[] of:

~/.openclaw/plugin-runtime-deps/openclaw-/.openclaw-runtime-deps.json

```

The nine listed above appear in dist imports but are not in the manifest `specs`.

Approach

These deps fit the same pattern as the existing `semver` / `tslog` entries: declared in the openclaw root `package.json` and used by core dist code. Adding them to `MIRRORED_CORE_RUNTIME_DEP_NAMES` is the smallest, most consistent fix.

A more durable fix would derive the list from a static analysis of core dist bare-imports during build (or extend `collectRootDistMirroredRuntimeDeps` to treat root `package.json` as a fallback owner) so the allowlist cannot drift as new core deps are added. I left that as a follow-up to keep this PR mechanical and easy to review.

Refs

Test plan

  • `pnpm test src/plugins/bundled-runtime-deps.test.ts` — 87 passed
  • `pnpm test src/plugins/stage-bundled-plugin-runtime-deps.test.ts src/commands/doctor-bundled-plugin-runtime-deps.test.ts src/infra/package-dist-inventory.test.ts` — all passed
  • `pnpm format:check src/plugins/bundled-runtime-deps.ts CHANGELOG.md` — clean
  • Verify against a fresh install: install on a host without any of the nine packages currently in the mirror, then trigger code paths (e.g. `openclaw cron list`, push notification path, backup) and confirm no `Cannot find package` errors.

🤖 Generated with Claude Code

@greptile-apps

greptile-apps Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends MIRRORED_CORE_RUNTIME_DEP_NAMES in src/plugins/bundled-runtime-deps.ts with nine additional root-package runtime dependencies (@agentclientprotocol/sdk, croner, dotenv, jiti, json5, jszip, markdown-it, tar, web-push) that are imported by core dist code but were previously missing from the mirror allowlist, preventing Cannot find package errors in setups where no enabled extension owns the dependency. All nine packages are confirmed in the root package.json, the list is sorted alphabetically, and the existing semver/tslog entries are preserved.

Confidence Score: 5/5

Safe to merge — mechanical allowlist extension with all entries verified against root package.json and tests passing.

Single-file logic change adds nine entries to a string array constant; all nine packages are confirmed as declared root-level dependencies; the consuming code at line 679–681 already silently skips any entry not found in runtimeDeps; alphabetical ordering is maintained; no control flow or API surface changes.

No files require special attention.

Reviews (1): Last reviewed commit: "fix(plugins): mirror core root-package d..." | Re-trigger Greptile

@clawsweeper

clawsweeper Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Keep this PR open. Current main still mirrors only semver and tslog for core runtime deps, while the paired bug report #74199 and current source show additional root runtime dependencies imported by core paths. The author’s follow-up usefully adds a drift guard and expands the PR from nine to ten deps, so this is an active implementation candidate that needs maintainer review, CI, and packaged/global-install proof rather than cleanup closure.

Maintainer follow-up before merge:

Keep this PR open and review it with #74199. The maintainers should decide whether to land the narrow allowlist expansion plus drift guard, or replace it with a more durable root-package/dist-import derivation. Before merge, require green focused tests and packaged/global-install or Testbox smoke proving the runtime-deps manifest includes the added root deps and a representative staged core path no longer throws Cannot find package.

Best possible solution:

Keep this PR open and review it with #74199. The maintainers should decide whether to land the narrow allowlist expansion plus drift guard, or replace it with a more durable root-package/dist-import derivation. Before merge, require green focused tests and packaged/global-install or Testbox smoke proving the runtime-deps manifest includes the added root deps and a representative staged core path no longer throws Cannot find package.

Acceptance criteria:

  • pnpm test src/plugins/bundled-runtime-deps.test.ts
  • pnpm test src/plugins/stage-bundled-plugin-runtime-deps.test.ts src/commands/doctor-bundled-plugin-runtime-deps.test.ts src/infra/package-dist-inventory.test.ts
  • pnpm format:check src/plugins/bundled-runtime-deps.ts CHANGELOG.md
  • Packaged/global-install or Testbox smoke proving .openclaw-runtime-deps.json includes the added root deps and a representative staged core path resolves without Cannot find package.

What I checked:

  • Current main allowlist is still minimal: MIRRORED_CORE_RUNTIME_DEP_NAMES on current main is still exactly ["semver", "tslog"]; the PR's ten added packages are not implemented on main. (src/plugins/bundled-runtime-deps.ts:75, 68ba1e7180a6)
  • Collector only mirrors fixed core deps plus plugin-owned dist imports: collectMirroredPackageRuntimeDeps adds the fixed core allowlist, then calls collectRootDistMirroredRuntimeDeps; the root dist scan skips imports without a bundled-plugin dependency owner, which matches the reported missing-core-dep failure mode. (src/plugins/bundled-runtime-deps.ts:669, 68ba1e7180a6)
  • Reported packages are root runtime dependencies: Root package.json declares the reported dependencies, including @agentclientprotocol/sdk, @lydell/node-pty, croner, dotenv, jiti, json5, jszip, markdown-it, tar, and web-push. Extension manifests only cover examples like chokidar, jiti, and markdown-it when those plugins own them. (package.json:1603, 68ba1e7180a6)
  • Current source imports the missing deps as values: Current source has representative value imports or dynamic imports for the missing deps, including croner in cron scheduling, dotenv in config/infra dotenv paths, json5 in config/shared parsing, jszip/tar in archive and backup paths, jiti in plugin loading, markdown-it in markdown IR, web-push in push support, @agentclientprotocol/sdk in ACP paths, and @lydell/node-pty in the PTY adapter. (src/cron/schedule.ts:1, 68ba1e7180a6)
  • Main coverage has only the older core-dep regression case: Current tests verify the existing tslog mirrored-core behavior for startup plugins without their own deps, but current main does not yet have the PR's static drift guard for root-package bare imports. (src/plugins/bundled-runtime-deps.test.ts:1290, 68ba1e7180a6)
  • PR diff is focused and security-sensitive surfaces are not broadened unexpectedly: Provided PR file data for head 8e7452a578c555577e864b00ed2489f697362f06 shows three changed files: the runtime-deps allowlist, a colocated test drift guard, and a changelog entry. It does not change workflows, lockfiles, package sources, install scripts, release scripts, or package manager metadata; the security pass found no unrelated code-execution or supply-chain change beyond mirroring already-declared root deps. (src/plugins/bundled-runtime-deps.ts:75, 8e7452a578c5)

Likely related people:

  • @steipete: Current-main blame and history show Peter Steinberger introduced the existing MIRRORED_CORE_RUNTIME_DEP_NAMES allowlist, the collector logic, and the runtime-deps tests in 9b1967e5, then recently touched the runtime-deps cleanup path in 7877182b. (role: recent maintainer and likely owner of the current runtime-deps implementation; confidence: high; commits: 9b1967e5efe2, 7877182b6f59; files: src/plugins/bundled-runtime-deps.ts, src/plugins/bundled-runtime-deps.test.ts, package.json)
  • @vincentkoc: History search for root deps such as croner and @lydell/node-pty surfaces Vincent Koc's recent source/package graph touch involving cron and package dependency surfaces; route here only if review needs domain context on those consuming paths rather than the runtime-deps mirror itself. (role: adjacent maintainer for dependency-consuming paths; confidence: low; commits: b69b508d206b; files: src/cron/schedule.ts, src/process/supervisor/adapters/pty.ts, package.json)

Remaining risk / open question:

  • Closing would be premature because the PR's intended source change and new drift guard are not present on current main.
  • The PR now mirrors @lydell/node-pty, a native dependency already in root package.json; maintainer review should include packaged/global-install or Testbox proof that runtime-deps staging handles it on supported platforms.
  • The PR is paired with open bug [Bug]: runtime-deps manifest still missing 9 dist bare-imports in 2026.4.26 (follow-up to #71420) #74199, so the best cleanup path is normal review/merge-or-reject rather than auto-closing one half of the pair.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 68ba1e7180a6.

@maxpuppet
maxpuppet force-pushed the fix/runtime-deps-mirror-core-imports branch from 175eaf8 to 56297e8 Compare April 29, 2026 08:34
Extend MIRRORED_CORE_RUNTIME_DEP_NAMES from ["semver", "tslog"] to
also include @agentclientprotocol/sdk, @lydell/node-pty, croner,
dotenv, jiti, json5, jszip, markdown-it, tar, and web-push.

These are all declared as direct dependencies in the openclaw root
package.json and imported by core source code (src/acp/*, src/cron/*,
src/config/*, src/infra/{archive,backup,dotenv,push-web}.ts,
src/markdown/ir.ts, src/plugin-sdk/root-alias.cjs,
src/plugins/jiti-loader-cache.ts, src/process/supervisor/adapters/pty.ts,
etc), but the existing collectMirroredPackageRuntimeDeps allowlist only
covered semver and tslog.

The dynamic collectRootDistMirroredRuntimeDeps scan does pick up
imports that have an extension package.json owner (for example
memory-core declares chokidar, matrix declares jiti and markdown-it).
For deps with no extension owner, or for setups where the owning
extension is not enabled, those imports never make it into the
runtime-deps mirror and Node fails to resolve them at runtime, e.g.:

    Cannot find package 'chokidar' imported from
    .../plugin-runtime-deps/openclaw-<ver>/dist/qmd-manager-...js

Also add a static drift guard test that walks src/ for value imports of
root-package runtime deps and fails when one is neither in
MIRRORED_CORE_RUNTIME_DEP_NAMES nor declared by any extension's
package.json (with an explicit allowlist for known-transitive or
build/type-only imports such as chalk, ipaddr.js, file-type,
proxy-agent, typescript, qrcode). The guard caught @lydell/node-pty
during this change.

Refs openclaw#74199.
@maxpuppet
maxpuppet force-pushed the fix/runtime-deps-mirror-core-imports branch from 56297e8 to 8e7452a Compare April 29, 2026 08:44
@maxpuppet

Copy link
Copy Markdown
Author

Pushed an update addressing the bot review.

What changed since the last commit (175eaf8):

  1. Added a static drift guard test (MIRRORED_CORE_RUNTIME_DEP_NAMES drift guard in src/plugins/bundled-runtime-deps.test.ts). It walks non-test files under src/, extracts value imports (skips import type), filters node builtins / openclaw/* aliases / known-transitive resolution, and asserts every remaining root-package.json runtime dep is mirrored or extension-owned.
  2. The guard caught @lydell/node-pty (dynamic import in src/process/supervisor/adapters/pty.ts) — it's a real value import of a root dep that wasn't getting mirrored. Added it to MIRRORED_CORE_RUNTIME_DEP_NAMES. So this PR now mirrors ten packages (was nine) and includes the regression guard the review asked for.
  3. The allowlist KNOWN_UNMIRRORED_BARE_IMPORTS is intentionally narrow with an inline comment per entry (transitively-resolved packages, type-only imports, build/CLI-only tooling). When a future contributor adds a new core dep, the guard fails with a message pointing them at the three valid next steps (mirror it, own it from an extension, or document why it doesn't need either).

I think that addresses asks #1 (regression test) and #2 (durable static check) from the codex review. The remaining ask #3 (packaged/global-install smoke) needs an environment I can't drive from this PR — happy to add a Testbox follow-up if a maintainer can point me at the right command.

@fabianwilliams fabianwilliams left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Adding the 10 missing root-package runtime deps to MIRRORED_CORE_RUNTIME_DEP_NAMES is the right immediate fix, but the real value here is the drift-guard test — scanning src/ for value imports of root-package deps and failing CI when one isn't in either the mirror allowlist or extension-owned set is the discipline that closes the loop on a class of Cannot find package 'X' bugs (croner, jiti, json5, markdown-it, tar, web-push, etc. were exactly this shape). The KNOWN_UNMIRRORED_BARE_IMPORTS allowlist with per-entry reason comments is the right escape hatch for the deliberate exceptions.

@fabianwilliams
fabianwilliams merged commit 4d73cd5 into openclaw:main Apr 29, 2026
68 of 69 checks passed
lxe pushed a commit to lxe/openclaw that referenced this pull request May 6, 2026
…penclaw#74213)

Extend MIRRORED_CORE_RUNTIME_DEP_NAMES from ["semver", "tslog"] to
also include @agentclientprotocol/sdk, @lydell/node-pty, croner,
dotenv, jiti, json5, jszip, markdown-it, tar, and web-push.

These are all declared as direct dependencies in the openclaw root
package.json and imported by core source code (src/acp/*, src/cron/*,
src/config/*, src/infra/{archive,backup,dotenv,push-web}.ts,
src/markdown/ir.ts, src/plugin-sdk/root-alias.cjs,
src/plugins/jiti-loader-cache.ts, src/process/supervisor/adapters/pty.ts,
etc), but the existing collectMirroredPackageRuntimeDeps allowlist only
covered semver and tslog.

The dynamic collectRootDistMirroredRuntimeDeps scan does pick up
imports that have an extension package.json owner (for example
memory-core declares chokidar, matrix declares jiti and markdown-it).
For deps with no extension owner, or for setups where the owning
extension is not enabled, those imports never make it into the
runtime-deps mirror and Node fails to resolve them at runtime, e.g.:

    Cannot find package 'chokidar' imported from
    .../plugin-runtime-deps/openclaw-<ver>/dist/qmd-manager-...js

Also add a static drift guard test that walks src/ for value imports of
root-package runtime deps and fails when one is neither in
MIRRORED_CORE_RUNTIME_DEP_NAMES nor declared by any extension's
package.json (with an explicit allowlist for known-transitive or
build/type-only imports such as chalk, ipaddr.js, file-type,
proxy-agent, typescript, qrcode). The guard caught @lydell/node-pty
during this change.

Refs openclaw#74199.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…penclaw#74213)

Extend MIRRORED_CORE_RUNTIME_DEP_NAMES from ["semver", "tslog"] to
also include @agentclientprotocol/sdk, @lydell/node-pty, croner,
dotenv, jiti, json5, jszip, markdown-it, tar, and web-push.

These are all declared as direct dependencies in the openclaw root
package.json and imported by core source code (src/acp/*, src/cron/*,
src/config/*, src/infra/{archive,backup,dotenv,push-web}.ts,
src/markdown/ir.ts, src/plugin-sdk/root-alias.cjs,
src/plugins/jiti-loader-cache.ts, src/process/supervisor/adapters/pty.ts,
etc), but the existing collectMirroredPackageRuntimeDeps allowlist only
covered semver and tslog.

The dynamic collectRootDistMirroredRuntimeDeps scan does pick up
imports that have an extension package.json owner (for example
memory-core declares chokidar, matrix declares jiti and markdown-it).
For deps with no extension owner, or for setups where the owning
extension is not enabled, those imports never make it into the
runtime-deps mirror and Node fails to resolve them at runtime, e.g.:

    Cannot find package 'chokidar' imported from
    .../plugin-runtime-deps/openclaw-<ver>/dist/qmd-manager-...js

Also add a static drift guard test that walks src/ for value imports of
root-package runtime deps and fails when one is neither in
MIRRORED_CORE_RUNTIME_DEP_NAMES nor declared by any extension's
package.json (with an explicit allowlist for known-transitive or
build/type-only imports such as chalk, ipaddr.js, file-type,
proxy-agent, typescript, qrcode). The guard caught @lydell/node-pty
during this change.

Refs openclaw#74199.
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…penclaw#74213)

Extend MIRRORED_CORE_RUNTIME_DEP_NAMES from ["semver", "tslog"] to
also include @agentclientprotocol/sdk, @lydell/node-pty, croner,
dotenv, jiti, json5, jszip, markdown-it, tar, and web-push.

These are all declared as direct dependencies in the openclaw root
package.json and imported by core source code (src/acp/*, src/cron/*,
src/config/*, src/infra/{archive,backup,dotenv,push-web}.ts,
src/markdown/ir.ts, src/plugin-sdk/root-alias.cjs,
src/plugins/jiti-loader-cache.ts, src/process/supervisor/adapters/pty.ts,
etc), but the existing collectMirroredPackageRuntimeDeps allowlist only
covered semver and tslog.

The dynamic collectRootDistMirroredRuntimeDeps scan does pick up
imports that have an extension package.json owner (for example
memory-core declares chokidar, matrix declares jiti and markdown-it).
For deps with no extension owner, or for setups where the owning
extension is not enabled, those imports never make it into the
runtime-deps mirror and Node fails to resolve them at runtime, e.g.:

    Cannot find package 'chokidar' imported from
    .../plugin-runtime-deps/openclaw-<ver>/dist/qmd-manager-...js

Also add a static drift guard test that walks src/ for value imports of
root-package runtime deps and fails when one is neither in
MIRRORED_CORE_RUNTIME_DEP_NAMES nor declared by any extension's
package.json (with an explicit allowlist for known-transitive or
build/type-only imports such as chalk, ipaddr.js, file-type,
proxy-agent, typescript, qrcode). The guard caught @lydell/node-pty
during this change.

Refs openclaw#74199.
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…penclaw#74213)

Extend MIRRORED_CORE_RUNTIME_DEP_NAMES from ["semver", "tslog"] to
also include @agentclientprotocol/sdk, @lydell/node-pty, croner,
dotenv, jiti, json5, jszip, markdown-it, tar, and web-push.

These are all declared as direct dependencies in the openclaw root
package.json and imported by core source code (src/acp/*, src/cron/*,
src/config/*, src/infra/{archive,backup,dotenv,push-web}.ts,
src/markdown/ir.ts, src/plugin-sdk/root-alias.cjs,
src/plugins/jiti-loader-cache.ts, src/process/supervisor/adapters/pty.ts,
etc), but the existing collectMirroredPackageRuntimeDeps allowlist only
covered semver and tslog.

The dynamic collectRootDistMirroredRuntimeDeps scan does pick up
imports that have an extension package.json owner (for example
memory-core declares chokidar, matrix declares jiti and markdown-it).
For deps with no extension owner, or for setups where the owning
extension is not enabled, those imports never make it into the
runtime-deps mirror and Node fails to resolve them at runtime, e.g.:

    Cannot find package 'chokidar' imported from
    .../plugin-runtime-deps/openclaw-<ver>/dist/qmd-manager-...js

Also add a static drift guard test that walks src/ for value imports of
root-package runtime deps and fails when one is neither in
MIRRORED_CORE_RUNTIME_DEP_NAMES nor declared by any extension's
package.json (with an explicit allowlist for known-transitive or
build/type-only imports such as chalk, ipaddr.js, file-type,
proxy-agent, typescript, qrcode). The guard caught @lydell/node-pty
during this change.

Refs openclaw#74199.
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
…penclaw#74213)

Extend MIRRORED_CORE_RUNTIME_DEP_NAMES from ["semver", "tslog"] to
also include @agentclientprotocol/sdk, @lydell/node-pty, croner,
dotenv, jiti, json5, jszip, markdown-it, tar, and web-push.

These are all declared as direct dependencies in the openclaw root
package.json and imported by core source code (src/acp/*, src/cron/*,
src/config/*, src/infra/{archive,backup,dotenv,push-web}.ts,
src/markdown/ir.ts, src/plugin-sdk/root-alias.cjs,
src/plugins/jiti-loader-cache.ts, src/process/supervisor/adapters/pty.ts,
etc), but the existing collectMirroredPackageRuntimeDeps allowlist only
covered semver and tslog.

The dynamic collectRootDistMirroredRuntimeDeps scan does pick up
imports that have an extension package.json owner (for example
memory-core declares chokidar, matrix declares jiti and markdown-it).
For deps with no extension owner, or for setups where the owning
extension is not enabled, those imports never make it into the
runtime-deps mirror and Node fails to resolve them at runtime, e.g.:

    Cannot find package 'chokidar' imported from
    .../plugin-runtime-deps/openclaw-<ver>/dist/qmd-manager-...js

Also add a static drift guard test that walks src/ for value imports of
root-package runtime deps and fails when one is neither in
MIRRORED_CORE_RUNTIME_DEP_NAMES nor declared by any extension's
package.json (with an explicit allowlist for known-transitive or
build/type-only imports such as chalk, ipaddr.js, file-type,
proxy-agent, typescript, qrcode). The guard caught @lydell/node-pty
during this change.

Refs openclaw#74199.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Bundled runtime deps repeatedly reinstall / stall when packaged /app is non-writable

2 participants