fix(plugin-sdk): symlink openclaw peerDependencies after plugin install#70462
Conversation
Greptile SummaryThis PR adds a post-install step ( Confidence Score: 5/5Safe to merge — the symlink logic is correct and all remaining findings are minor style/clarity issues. The fix correctly addresses the runtime No files require special attention. Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/plugins/install.ts
Line: 826
Comment:
**`hasDeps` peerDeps guard is not required for symlinking**
`afterInstall` is invoked unconditionally in `installPackageDir` — the `if (params.hasDeps)` block (which gates `npm install`) and the `if (params.afterInstall)` block are independent. That means `linkOpenClawPeerDependencies` already runs even when `hasDeps` remains `false`, and `fs.mkdir(nodeModulesDir, { recursive: true })` inside the function creates the directory as needed. Including `peerDeps` in `hasDeps` causes a full (no-op) `npm install` to execute for plugins that declare only peerDependencies — which adds latency and a potential failure vector in network-restricted environments. Consider keeping the `hasDeps` check scoped to `dependencies` only.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/plugins/install.ts
Line: 643-653
Comment:
**Dead-code check in `linkTarget` resolution**
`linkTarget` is `hostRoot/node_modules/openclaw`. Because `hostRoot` is already the openclaw package root (found by walking ancestors until `package.json` has `name === "openclaw"`), the access check for `linkTarget/package.json` is testing whether openclaw depends on itself — which never happens. The fallback `resolvedTarget = hostRoot` is therefore always used for `"openclaw"`, making the `fs.access` branch dead code. The comment "Resolve the actual source for scoped packages" suggests future-proofing, but today it adds confusion. If the intent is to support future peers like `@openclaw/core` that genuinely live inside `hostRoot/node_modules`, this should be documented more explicitly, or the lookup should start from `hostRoot` for non-`"openclaw"` peers only.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/plugins/install.ts
Line: 619-622
Comment:
**`OPENCLAW_PEER_NAMES` allocated on every invocation**
`new Set(["openclaw"])` is created inside `linkOpenClawPeerDependencies` on every call. Hoisting it to a module-level constant avoids the allocation and makes the set of recognised peer names easier to discover and extend.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(plugin-sdk): symlink openclaw peerDe..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 962d2ed889
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9233fd44a7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Approach looks right to me — A few small things if you're iterating:
Also worth flagging that this fixes #54428 too (weixin plugin, same root cause) — might be worth adding a LGTM modulo the test addition. 👍 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 50761464c6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| await linkOpenClawPeerDependencies({ | ||
| installedDir, | ||
| peerDependencies: peerDeps, | ||
| logger, | ||
| }); | ||
| return await runInstallSourceScan({ |
There was a problem hiding this comment.
Run dependency-tree scan before linking host peer package
Linking node_modules/openclaw before the post-install dependency scan causes installs to fail for peer-based plugins: linkOpenClawPeerDependencies creates a symlink to the host package root (outside the staged plugin directory), and the scanner rejects any node_modules symlink target outside install root (src/plugins/install-security-scan.runtime.ts:197-200), so this path returns SECURITY_SCAN_FAILED instead of a successful install whenever the link is created.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a09e53ca77
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Thanks for the review! I addressed the points in Changes made
Additional fix uncovered by the new testsThe dependency-tree security scan was running after I reordered
Pre-existing malicious Leaving the semver compatibility check as a follow-up, per your suggestion. All 64 checks are passing. |
a09e53c to
ea2b5b4
Compare
## Summary Signed-off-by: anish k <ak8686@princeton.edu>
Tests three cases via installPluginFromDir: - symlink created when peerDependencies declares openclaw - no symlink when peer list is empty - idempotent re-install replaces existing symlink - warns and skips when host root cannot be resolved Also removes the single-element Set in favour of a direct name comparison (peerName === "openclaw"), and adds Closes openclaw#54428 to address the same root cause in the weixin connector. Closes openclaw#54428
…ymlink The dependency-tree security scan rejects node_modules symlinks whose targets resolve outside the install root. Our trusted host-to-plugin symlink violates that rule by design, so running the scan AFTER linkOpenClawPeerDependencies would fail every install with SECURITY_SCAN_FAILED. Reorder afterInstall so the scan runs first (walking only the plugin's own staged source, catching any pre-existing malicious openclaw-named symlink a source might smuggle in), then the trusted link is materialised on the now-safe tree. Also use braces on guard clauses in the new unit tests to satisfy the oxlint no-unreachable-single-statement-if rule.
ea2b5b4 to
1684686
Compare
|
Landed via temp rebase onto main.
Thanks @anishesg! |
Summary
openclawas apeerDependencyfail at runtime withCannot find package 'openclaw'because npm never materialises peerDependencies automatically duringnpm install --omit=dev.src/plugins/install.ts:installPluginFromPackageDironly inspectsmanifest.dependencieswhen deciding whether to run npm install (hasDeps) and has no post-install logic to satisfy peerDependencies.linkOpenClawPeerDependencies— a post-install step that reads the plugin'speerDependencies, finds the host openclaw package root viaresolveOpenClawPackageRootSync, and symlinks the matching package into the plugin'snode_modules/.hasDepsto betruewhenpeerDependenciesis non-empty so that npm install still runs (and therefore the staged directory is prepared) even for plugins with no regulardependencies.PackageManifestlocal type to includepeerDependencies?: Record<string, string>so the field is read from the plugin'spackage.json.Change Type
Scope
Linked Issue/PR
Closes #70436
Root Cause
Between plugin versions (e.g.
dingtalk-connectorv0.8.16 → v0.8.18)openclawwas moved fromdependenciestopeerDependencies. npm does not install peerDependencies in production installs, and the global openclaw directory is not on the plugin's Node.js module resolution path, so the plugin's bundled runtime cannot importopenclawat startup.Regression Test Plan
Unit: the
linkOpenClawPeerDependenciesfunction is pure async logic operating onfs— an existing test fixture insrc/infra/install-package-dir.test.ts(which already mocksfs) is the smallest seam to extend. The function is also exercised end-to-end wheneveropenclaw plugins installis run with a plugin whosepackage.jsondeclaresopenclawinpeerDependencies.User-visible / Behavior Changes
After
openclaw plugins install, a symlink<plugin>/node_modules/openclaw → <host-root>is created when the plugin declaresopenclawas a peerDependency. No behavior change for plugins that do not declare the peerDependency.Diagram
N/A
Security Impact
node_modules/subdirectory within the extensions dirRepro + Verification
Environment: macOS arm64, Node 20, openclaw 2026.4.21 (global npm install)
Steps:
openclaw plugins install @dingtalk-real-ai/dingtalk-connectorCannot find package 'openclaw' imported from .../runtime-CAQ2GHj-.mjsnode_modules/openclawsymlink is present in the plugin directoryEvidence
Error from issue:
Cannot find package 'openclaw' imported from /Users/zhouxia/.openclaw/extensions/dingtalk-connector/dist/runtime-CAQ2GHj-.mjsAfter fix, the plugin directory contains
node_modules/openclaw -> /opt/homebrew/lib/node_modules/openclaw.Human Verification
Verified: logic paths for missing
peerDependenciesfield (empty object fallback), missing host root (warn + continue), idempotent re-install (rm before symlink), and plugins with no openclaw peerDependency (early return). Did not verify Windows junction semantics end-to-end on a live Windows machine.Review Conversations
Fixes #70436
Closes #54428