Skip to content

fix: plan managed npm peer pins with npm#81450

Merged
steipete merged 11 commits into
mainfrom
codex/npm-peer-lock-resolver
May 14, 2026
Merged

fix: plan managed npm peer pins with npm#81450
steipete merged 11 commits into
mainfrom
codex/npm-peer-lock-resolver

Conversation

@steipete

@steipete steipete commented May 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • resolve managed plugin peer pins from npm's package-lock planner instead of recursively scanning node_modules
  • remove the installed-tree traversal limits/path-boundary scanner and use a temp npm root for peer planning
  • keep openclaw host peers out of managed root ownership, including temp-lock scrub/retry for optional host peer metadata
  • fold PR Preserve root-managed OpenClaw runtime during plugin installs #81632's active-host guard/test so root-managed installs preserve the currently running OpenClaw package; commit credits @fuller-stack-dev

Real behavior proof

Behavior addressed: Managed npm plugin installs now use npm's lockfile planner for peer pins, avoid promoting nested transitive versions into root peer deps, and preserve the active root-managed node_modules/openclaw host package instead of uninstalling it.

Real environment tested: Local macOS source checkout using real npm-managed-root install paths plus the repo static npm registry e2e; no production credentials or live user state.

Exact steps or command run after this patch: OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/test-projects.mjs src/plugins/install.npm-spec.e2e.test.ts -- --reporter=verbose

Evidence after fix: The npm install e2e shard passed 11 tests, including managed root host-peer cleanup and sibling peer relink behavior, after commit 3ab1e219ad50015d5637a0a9eececafc632dacf5.

Observed result after fix: Managed plugin install leaves the root node_modules/openclaw absent for normal peer materialization cleanup, relinks plugin-local node_modules/openclaw peers, and the folded active-host regression keeps an existing root-managed active host package instead of running npm uninstall openclaw.

What was not tested: No live third-party registry install against a real user ~/.openclaw directory; no package acceptance Docker lane was run for this PR.

Verification

  • pnpm check:test-types
  • pnpm exec oxfmt --check --threads=1 src/infra/npm-managed-root.ts src/infra/npm-managed-root.test.ts src/plugins/install.npm-spec.test.ts CHANGELOG.md && git diff --check
  • node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.core.json src/infra/npm-managed-root.ts src/infra/npm-managed-root.test.ts src/plugins/install.npm-spec.test.ts src/plugins/install.ts
  • OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test src/infra/npm-managed-root.test.ts src/plugins/install.npm-spec.test.ts -- --reporter=verbose
  • OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/test-projects.mjs src/plugins/install.npm-spec.e2e.test.ts -- --reporter=verbose
  • codex-review --full-access: clean, no accepted/actionable findings

Follow-up to #81435. Folds the active-host preservation guard/test from #81632.

@openclaw-barnacle openclaw-barnacle Bot added size: L maintainer Maintainer-authored PR labels May 13, 2026
@clawsweeper

clawsweeper Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge.

Summary
The PR replaces installed-tree managed peer discovery with npm lockfile planning, adds fallback/rollback handling, preserves active root-managed OpenClaw runtimes, updates managed npm tests, and adds a changelog entry.

Reproducibility: yes. Source inspection of PR head shows the fallback map copies prior managed peers without excluding openclaw; a manifest with dependencies.openclaw, openclaw.managedPeerDependencies: ["openclaw"], and any nonzero planner result would preserve the host peer instead of pruning it.

Real behavior proof
Override: The PR has proof: override; the body also includes terminal-style after-fix e2e proof, so the external contributor proof gate is maintainer-overridden.

Next step before merge
The remaining blocker is a narrow code/test repair in the managed npm fallback path and is suitable for a focused repair PR if a maintainer promotes it.

Security
Cleared: The diff changes npm planning/install internals but keeps lifecycle scripts disabled and does not add dependency sources, workflow permissions, secrets handling, or publishing changes.

Review findings

  • [P2] Filter host peers from fallback pins — src/infra/npm-managed-root.ts:516-520
Review details

Best possible solution:

Fix the fallback host-peer filtering with focused regression coverage, then land this PR after maintainer review and use it to supersede #81632 if the folded active-host guard remains included.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection of PR head shows the fallback map copies prior managed peers without excluding openclaw; a manifest with dependencies.openclaw, openclaw.managedPeerDependencies: ["openclaw"], and any nonzero planner result would preserve the host peer instead of pruning it.

Is this the best way to solve the issue?

No. Using npm's lockfile planner is a good direction, but the fallback needs to apply the same host-peer exclusion invariant before this is the narrowest maintainable fix.

Full review comments:

  • [P2] Filter host peers from fallback pins — src/infra/npm-managed-root.ts:516-520
    When the planner exits nonzero, this fallback copies every previously managed peer, including openclaw. Current main's scanner excludes openclaw, and this PR's stated invariant is that the managed root should not own host peers; with stale openclaw managed metadata plus an unrelated ERESOLVE, syncManagedNpmRootPeerDependencies keeps the host package in dependencies/metadata instead of pruning it.
    Confidence: 0.89

Overall correctness: patch is incorrect
Overall confidence: 0.88

Acceptance criteria:

  • node scripts/run-vitest.mjs run src/infra/npm-managed-root.test.ts --maxWorkers=1
  • node scripts/run-vitest.mjs run src/plugins/install.npm-spec.test.ts --maxWorkers=1
  • node scripts/test-projects.mjs src/plugins/install.npm-spec.e2e.test.ts
  • node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.core.json src/infra/npm-managed-root.ts src/infra/npm-managed-root.test.ts src/plugins/install.ts src/plugins/install.npm-spec.test.ts src/plugins/install.npm-spec.e2e.test.ts
  • pnpm exec oxfmt --check --threads=1 src/infra/npm-managed-root.ts src/infra/npm-managed-root.test.ts src/plugins/install.ts src/plugins/install.npm-spec.test.ts src/plugins/install.npm-spec.e2e.test.ts CHANGELOG.md && git diff --check

What I checked:

  • Current main baseline: Current main derives managed peer pins by scanning installed packages under node_modules; that scan skips the openclaw package and skips openclaw peer names, so stale host peers are not re-collected as managed peer pins. (src/infra/npm-managed-root.ts:393, 697103604367)
  • Latest PR planner path: At PR head a3ef4627d20ce23288370efb13800346c122aa44, collectNpmResolvedManagedNpmRootPeerDependencyPins creates fallback pins from previous managed peers before deleting dependencies.openclaw, and collectExistingManagedPeerDependencyPins copies each prior managed peer without filtering host package names. (src/infra/npm-managed-root.ts:511, a3ef4627d20c)
  • Review-comment provenance: The same fallback host-peer issue was already raised against the current PR head, and the GitHub review-comment API still reports it on current line 520 rather than as an outdated line. (src/infra/npm-managed-root.ts:520, a3ef4627d20c)
  • Coverage gap: The new fallback unit test covers preserving a non-host runtime-peer on third-party planner failure, but it does not cover a previous openclaw managed peer plus unrelated planner failure, which is the host-peer regression path. (src/infra/npm-managed-root.test.ts:508, a3ef4627d20c)
  • Live PR state: Unauthenticated GitHub API shows the PR open at head a3ef4627d20ce23288370efb13800346c122aa44 with labels maintainer, size: XL, and proof: override; the PR body includes the claimed real behavior proof and states that package acceptance Docker and a real user directory install were not tested. (a3ef4627d20c)
  • Related baseline history: The managed peer preservation baseline that this PR follows up on is present in merged commit 402b0df3b675407f786c0174510202f68f80d606, and nearby history shows the feature was introduced and hardened immediately before this PR. (src/infra/npm-managed-root.ts, 402b0df3b675)

Likely related people:

  • shakkernerd: Introduced and hardened the managed plugin peer dependency preservation baseline that this PR replaces with planner-based peer detection. (role: recent feature owner; confidence: high; commits: 18ca285ed6d1, 6e5042cd62e7, f4cb20300f6b; files: src/infra/npm-managed-root.ts, src/plugins/install.ts, src/infra/npm-managed-root.test.ts)
  • vincentkoc: Recent main history includes managed npm install fixes, and the PR discussion says vincentkoc rebased this PR and added the peer-conflict fallback commit. (role: recent area contributor and follow-up owner; confidence: high; commits: 3ba2ce6694d2, 5d557171b3cd, 0ddbf2e258d9; files: src/infra/npm-managed-root.ts, src/plugins/install.ts, src/infra/npm-managed-root.test.ts)
  • steipete: Current-main history shows prior plugin install hardening and npm pack/install work by Peter Steinberger in this path, and this PR continues that same managed npm runtime surface. (role: adjacent owner and PR author with prior merged history; confidence: high; commits: 5dc50b8a3f80, 8e533490ab0a, 2eaf8ad7126b; files: src/plugins/install.ts, src/infra/npm-managed-root.ts, src/plugins/install.npm-spec.test.ts)

Remaining risk / open question:

  • The fallback path is not covered for a previous openclaw managed peer plus unrelated npm planner failure.
  • The PR body says package acceptance Docker and a live install against a real user OpenClaw directory were not tested.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 971654813b

ℹ️ 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".

Comment thread src/infra/npm-managed-root.ts Outdated
Comment on lines +505 to +507
let result = await command(npmPeerPlanArgs, npmPlanOptions);
if (result.code !== 0 && isHostPeerResolutionFailure(result)) {
const hydrateResult = await command(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle non-openclaw peer failures in peer-plan step

The new peer-pin planner now aborts on any npm resolution error unless the stderr/stdout happens to mention openclaw@, because only that case enters the retry path. This is a regression for plugins with conflicting or unavailable other peer dependencies: npm install --package-lock-only --omit=peer still validates peer resolution, so the plan step can fail even though the real managed-root install path intentionally uses --legacy-peer-deps and would otherwise proceed. In those cases, plugin install now fails before install/retry logic runs, instead of producing peer pins from the manifest tree as before.

Useful? React with 👍 / 👎.

@vincentkoc

Copy link
Copy Markdown
Member

Thanks @steipete. I rebased this on current main and pushed a follow-up commit for the ClawSweeper peer-conflict blocker.

What changed:

  • keeps the npm lockfile planner path for managed peer pins
  • preserves existing OpenClaw-managed peer pins if npm cannot strictly plan unrelated third-party peer conflicts
  • lets the existing legacy --legacy-peer-deps install path continue instead of failing before install
  • updates the e2e expectation to match planner-derived resolved peer pins

Verification:

  • node_modules/.bin/oxfmt --check src/infra/npm-managed-root.ts src/infra/npm-managed-root.test.ts src/plugins/install.npm-spec.e2e.test.ts
  • node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.core.json src/infra/npm-managed-root.ts src/infra/npm-managed-root.test.ts src/plugins/install.npm-spec.e2e.test.ts
  • node scripts/run-vitest.mjs run src/infra/npm-managed-root.test.ts --maxWorkers=1
  • node scripts/test-projects.mjs src/plugins/install.npm-spec.e2e.test.ts
  • git diff --check
  • Testbox changed gate: tbx_01krjdyj4d65wacan4qsw51e7h, https://github.com/openclaw/openclaw/actions/runs/25842793756

@steipete steipete force-pushed the codex/npm-peer-lock-resolver branch from 4456242 to 593c8f0 Compare May 14, 2026 07:00

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 555a56ae16

ℹ️ 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".

Comment on lines +454 to +458
for (const packageName of previousManagedPeerDependencies) {
const dependencySpec = dependencies[packageName];
if (dependencySpec) {
pins[packageName] = dependencySpec;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude host peer from fallback peer pins

When npm peer planning exits non-zero, syncManagedNpmRootPeerDependencies uses this fallback map, but the fallback copies prior managed peers verbatim. That means a stale openclaw managed peer is preserved instead of being pruned, so any planning failure (for example, unrelated ERESOLVE) can keep the managed root owning the host package even though this change is intended to keep host peers out of managed ownership.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fda4001730

ℹ️ 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".

Comment thread src/infra/npm-managed-root.ts Outdated
Comment on lines +335 to +339
for (const [location, value] of Object.entries(params.lockfile.packages).toSorted(
([left], [right]) => left.localeCompare(right),
)) {
if (
readLockPackageName(location, value) !== params.packageName ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Limit peer version pinning to root lockfile entries

The fallback loop in findLockPackageVersion now selects the first matching package version from any lockfile path, so when a peer package is missing at node_modules/<peer> but exists only as a nested transitive (for example node_modules/a/node_modules/<peer>), that nested version is pinned into the managed root. In that case we can pin an incompatible version (e.g. nested 1.x for a peer range requiring ^2) and then syncManagedNpmRootPeerDependencies preserves that bad pin across install passes, which regresses the previous root-only lookup behavior and can leave plugins installed with unsatisfied peers.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 55ad834e3a

ℹ️ 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".

Comment thread src/infra/npm-managed-root.ts Outdated
Comment on lines 361 to 363
if (location === "" || !isRecord(value) || isDevOnlyLockPackage(value)) {
continue;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip optional-only lock packages when deriving peer pins

collectNpmLockPeerDependencyPins now harvests peers from every non-dev lockfile entry, but this filter does not exclude optional-only packages that may appear in package-lock.json even when they are not materialized on the current OS/CPU. In that case, peers from a platform-specific optional package can be promoted into managed root dependencies and then enforced by later install passes, creating unnecessary or failing peer installs that did not occur with the previous installed-tree scan.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ab1e219ad

ℹ️ 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".

}

async function scrubHostPeerFromTempPackageLock(lockPath: string): Promise<void> {
const parsed = await readJsonIfExists<unknown>(lockPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Tolerate malformed lockfiles during peer-plan setup

The new peer-planning path unconditionally parses the copied package-lock.json via readJsonIfExists before running npm, and any JSON parse error now aborts syncManagedNpmRootPeerDependencies (which then fails plugin install/rollback in install.ts). This is a regression from the prior node_modules scan path, which did not require a valid lockfile to proceed; a stale/truncated lockfile should be treated as recoverable (skip scrub/copy and let npm regenerate) rather than a hard failure.

Useful? React with 👍 / 👎.

@steipete steipete added the proof: override Maintainer override for the external PR real behavior proof gate. label May 14, 2026
@steipete steipete force-pushed the codex/npm-peer-lock-resolver branch from e97fc88 to a3ef462 Compare May 14, 2026 20:01
@steipete

Copy link
Copy Markdown
Contributor Author

Verification for current head a3ef4627d20ce23288370efb13800346c122aa44:

Local / Codex proof:

  • codex-review --full-access (clean; no accepted/actionable findings)
  • pnpm check:test-types
  • pnpm exec oxfmt --check --threads=1 src/infra/npm-managed-root.ts src/infra/npm-managed-root.test.ts src/plugins/install.npm-spec.test.ts CHANGELOG.md test/scripts/mantis-build-telegram-desktop-proof-evidence.test.ts && git diff --check
  • node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.core.json src/infra/npm-managed-root.ts src/infra/npm-managed-root.test.ts src/plugins/install.npm-spec.test.ts src/plugins/install.ts (blocked locally after rebase by unrelated current-main plugin-sdk boundary prep errors in src/plugin-sdk/extension-shared.ts, src/plugin-sdk/file-lock.ts, and src/proxy-capture/env.ts)
  • OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test src/infra/npm-managed-root.test.ts src/plugins/install.npm-spec.test.ts -- --reporter=verbose (49 tests passed)
  • OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/test-projects.mjs src/plugins/install.npm-spec.e2e.test.ts -- --reporter=verbose (11 tests passed)
  • node scripts/run-vitest.mjs run --config test/vitest/vitest.full-core-support-boundary.config.ts test/scripts/mantis-build-telegram-desktop-proof-evidence.test.ts --reporter=verbose (1 test passed, confirms the prior merge-context Mantis failure is resolved by rebasing onto current main)

GitHub / remote proof:

Current GitHub state at verification time:

  • 55 current-head checks completed.
  • 0 current-head failures/cancellations/timeouts/action-required checks.
  • 36 Blacksmith-backed checks still queued without runner assignment after extended wait; auxiliary Testbox changed-check request tbx_01krm28m6p6gpekb2vg1vx7132 also remained queued for ~6 minutes and was stopped.

Known proof gaps:

  • No live user ~/.openclaw environment test.
  • No package acceptance / Docker release lane.
  • Some Blacksmith-backed CI checks remained capacity-queued, not failed, at merge decision time.

PR #81632's active-host guard/test has been folded into this branch. Contributor credit is preserved with Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com> and the changelog thanks @fuller-stack-dev.

@steipete steipete merged commit d9ff8cf into main May 14, 2026
91 checks passed
@steipete steipete deleted the codex/npm-peer-lock-resolver branch May 14, 2026 20:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer Maintainer-authored PR proof: override Maintainer override for the external PR real behavior proof gate. size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants