fix(plugins): reject hollow installs and repair missing dependencies#103398
fix(plugins): reject hollow installs and repair missing dependencies#103398nxmxbbd wants to merge 15 commits into
Conversation
…dencies npm can exit 0 while leaving the installed plugin's declared dependency tree unmaterialized; the hollow install previously passed lock-metadata verification and loaded at startup only to die at import time. Verify the declared required dependencies resolve from the installed package dir and roll the managed root back when they do not.
…d dependencies Status surfaces already point users at doctor --fix when an installed plugin's dependency tree is broken, but doctor only recognized entry diagnostics and stale runtime packages as repairable. Walk record-backed snapshot plugins with buildPluginDependencyStatus, surface the missing package names in the health finding, and route the plugin through the existing repairable-install flows so --fix reinstalls the package.
|
Codex review: needs maintainer review before merge. Reviewed July 15, 2026, 11:50 PM ET / July 16, 2026, 03:50 UTC. Summary PR surface: Source +202, Tests +741. Total +943 across 6 files. Reproducibility: yes. from source and supplied evidence, though not as a newly executed current-main failure in this review. An active managed npm record can point to a plugin tree missing a declared required dependency, and the contributor demonstrates that persisted corruption through the real doctor path. Review metrics: none identified. Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Land the install-time dependency gate and record-scoped doctor repair after refreshing onto current main and obtaining a green or clearly unrelated exact-head required-check result. Do we have a high-confidence way to reproduce the issue? Yes from source and supplied evidence, though not as a newly executed current-main failure in this review. An active managed npm record can point to a plugin tree missing a declared required dependency, and the contributor demonstrates that persisted corruption through the real doctor path. Is this the best way to solve the issue? Yes. Checking dependencies while the managed install rollback boundary remains active and repairing existing record-backed corruption through doctor is narrower and safer than gateway-startup import probing or a parallel installer. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 957cc81175a3. Label changesLabel justifications:
Evidence reviewedPR surface: Source +202, Tests +741. Total +943 across 6 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
Merge current upstream main into PR openclaw#103398 while retaining the validated install and doctor corrections from the refresh.
Closes #101049
What Problem This Solves
Fixes an issue where installing a channel plugin through the managed npm flow could report success with an empty or incomplete dependency tree. The plugin then appeared installed and loaded at startup, but its provider failed when importing a missing runtime dependency. In the reported Microsoft Teams case, webhook deliveries failed while the local install still looked healthy, and only a manual npm reinstall inside the managed project restored the channel.
Why This Change Was Made
The managed npm install path verified lockfile metadata, platform packages, and peer links after
npm install, but it did not verify that the installed plugin's own declared runtime dependencies resolved from the installed package root. Doctor likewise did not classify an installed-but-hollow dependency tree as repairable, even though plugin status already exposed the dependency state.This change reuses the existing plugin dependency-status machinery in both paths:
package.jsonnpm places in published packages, so an empty residual directory is not mistaken for an installed dependency.source:npmplugin roots that canonically match their persisted install record, names missing packages incore/doctor/configured-plugin-installs, and sends affected installs through the retention-marker-backed package updater. This includes catalog-backed official installs, so same-version repairs activate a fresh generation instead of reusing the hollow tree.optionalDependenciesoverride same-namedependencies, so packages that intentionally make a dependency optional are not rejected when npm legitimately omits it.Missing optional dependencies do not fail either path. Resolution follows Node walk-up semantics, so a required dependency hoisted to the managed project root remains valid. Import probing at every gateway startup is outside this change; the install path covers new writes and doctor covers persisted corruption.
User Impact
A managed plugin install with a hollow dependency tree now fails loudly instead of leaving a plugin that appears healthy but cannot run. Existing corrupted npm installs are reported by doctor with the missing package names, and
doctor --fixreinstalls the package into a healthy generation instead of requiring manual npm commands in an internal managed directory. Packages that list the same package in bothdependenciesandoptionalDependencieskeep npm's override behavior and remain installable when that dependency is absent.Evidence
Full E2E: partial. The persisted-corruption detection and repair path is direct CLI E2E. The install-time hollow-tree fault remains a production-function integration test because real npm cannot be made to deterministically exit successfully while omitting a required dependency.
Atomic proof status
ok: true) and GREEN here with the missing package named.optionalDependenciescan override same-namedependenciesand that npm may exit successfully while omitting the package.ms, after which focused doctor returned one finding namingms.doctor --fixfetched npm metadata and the package tarball, switched the install record to a fresh activation generation, restored[email protected], and left focused doctor clean.specwas RED because doctor cleared its marker but emitted no warning or failed id; mixed-batch coverage now reports that failure while an unrelated repair completes and keeps its retained marker.MODULE_NOT_FOUNDfor the residual directory; install and doctor/status regressions now require the dependency package manifest.Real behavior proof
OPENCLAW_STATE_DIR, and a localhost-only npm registry serving real metadata and a tarball for[email protected]with bundled[email protected].source:npminstall record whose active path is already a managed generation; move its plugin-localnode_modules/msaside; run focused doctor lint; rundoctor --fix --yes --non-interactive --no-workspace-suggestionswith npm pointed at the fresh local registry; independently read the updated SQLite record and resolve the restored dependency; rerun focused doctor lint.$STATE_DIR:Current main merge refresh
The branch carries two signed merge commits after the reviewed head. Merge
704e5a2fbfd35ceefb79664f88a4d77c783b07barefreshed the then-current main4ae4b5c2a464608045e29fe72793dbb6fbbcdad7with the reviewed six-file patch byte-identical. Merged5ebd2c9faf5e19d4b7a48ec102e46c59a1ae211resolves conflicts against current main9497450511d2996dae6632305ee2111a93377657, where the plugin install split (#107960) moved the managed npm install flow out ofsrc/plugins/install.tsinto focused modules. The install-time dependency gate now sits at the same anchor insideinstallPluginFromManagedNpmRootinsrc/plugins/install-managed-npm.ts; the ported import and gate lines are byte-identical to the reviewed change,src/plugins/install.tsis byte-identical to main's re-export barrel and leaves the diff, and the other five changed files carry the reviewed change merged with current main.The exact merged head passed the direct affected suites and repository changed-file gate:
check:changedincluded core production/test typechecks, conflict-marker and dependency guards, changed-file lint, database-first/media/runtime-sidecar guards, and runtime import-cycle checks. One lane is excluded: the max-lines ratchet fails via a stale main baseline entry forsrc/agents/modes/interactive/theme/theme.ts(file and baseline byte-identical to main here); all other lanes passed with that entry pruned locally. Hosted checks for this new head are pending and should be judged from this head's check runs.Scope, compatibility, and risk
No dependency, lockfile, configuration schema, authentication, permissions, network access, or persisted-record schema changes are introduced. The behavior change is limited to rejecting required dependencies that do not resolve after a managed npm install, preserving npm's optional override semantics during dependency status normalization, and treating the same persisted missing-required condition as a repairable doctor finding for the active matching npm install record. Path/archive records and same-id configured shadows are not advertised as auto-repairable. Optional dependencies, same-name optional overrides, and valid hoisted dependencies have explicit non-regression coverage. The main risks are false attribution and reusing an already-hollow same-version project; canonical root/source eligibility tests, fresh-generation retention tests, real nested-dependency installs, optional-override coverage, and direct repeated-generation repair E2E cover those boundaries.
AI-assisted; contributor manually reviewed and is responsible for the change.