fix(env-installer): suppress 'Installing config dependencies...' on no-op installs#11766
Conversation
…en work is actually being done Previously the message was emitted unconditionally for every config dependency, before any of the "do we need to fetch / re-symlink?" checks. As a result the banner printed on every install even when everything was already cached and correctly linked. Emit the started event lazily — at most once per install, and only when an orphan is being removed, a parent or subdep needs fetching, a parent symlink needs (re)creating, or orphan subdep siblings are being pruned. --- Written by an agent (Claude Code, claude-opus-4-7).
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📜 Recent review details🧰 Additional context used📓 Path-based instructions (2)**/*.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.test.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (1)📚 Learning: 2026-05-14T09:04:00.133ZApplied to files:
🔇 Additional comments (2)
📝 WalkthroughWalkthroughCentralizes emission of the "Installing config dependencies..." started event behind a guarded ChangesConditional logging for config dependency installation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labelsarea: config dependencies Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR refines pnpm’s default reporter output for config dependency installation by ensuring the Installing config dependencies... banner is only shown when installConfigDeps actually performs meaningful filesystem work, avoiding noisy output on fully idempotent installs.
Changes:
- Add a
reportStarted()helper to emit theinstalling-config-depsstartedlog at most once perinstallConfigDepsinvocation. - Gate
startedemission behind specific cleanup/fetch/relink actions (including optional subdep orphan cleanup and fetching). - Add a changeset to release the behavior change as a patch for
@pnpm/installing.env-installerandpnpm.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| installing/env-installer/src/installConfigDeps.ts | Adds one-time started emission and threads it into optional subdep installation to suppress the banner on true no-op runs. |
| .changeset/quiet-config-deps.md | Declares patch releases and documents the reporter-output behavior change. |
Comments suppressed due to low confidence (1)
installing/env-installer/src/installConfigDeps.ts:295
reportStartedis only called on fetch/import and rimraf paths, butinstallOptionalSubdepscan still do real filesystem work by (re)creating sibling symlinks viasymlinkDir(e.g. when a sibling link is missing/stale but the package is already in the store). In that case this change will suppress the "Installing config dependencies..." banner even though work was performed. Consider usingsymlinkDir’s return value ({ reused }) to callreportStartedonly when the link was actually created/replaced, so no-op symlink checks stay quiet but repairs are still reported.
await Promise.all(compatibleSubdeps.map(async (subdep) => {
const subdepFullPkgId = `${subdep.name}@${subdep.version}:${subdep.resolution.integrity}`
const subdepRelPath = calcLeafGlobalVirtualStorePath(subdepFullPkgId, subdep.name, subdep.version)
const subdepDirInGlobalVirtualStore = path.join(opts.globalVirtualStoreDir, subdepRelPath, 'node_modules', subdep.name)
if (!fs.existsSync(path.join(subdepDirInGlobalVirtualStore, 'package.json'))) {
opts.reportStarted()
const { fetching } = await opts.store.fetchPackage({
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
installing/env-installer/src/installConfigDeps.ts (1)
310-312: 💤 Low valueConsider whether subdep symlink creation should trigger the banner.
The code calls
symlinkDirfor subdeps (line 312) without a directreportStarted()call. In contrast, parent symlink creation (line 119) triggersreportStarted()on line 114 when the symlink is missing or incorrect. This asymmetry means:
- If a parent symlink is missing → banner appears
- If a subdep symlink is missing (but package exists) → silent operation
This appears intentional based on the PR description listing only five
reportStarted()call sites without mentioning subdep symlink creation, but it's worth confirming this design choice.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@installing/env-installer/src/installConfigDeps.ts` around lines 310 - 312, The subdependency symlink creation using symlinkDir for subdeps (where linkPath = path.join(opts.parentNodeModulesDir, subdep.name)) currently doesn't call reportStarted(), unlike the parent symlink branch that calls reportStarted() around parent symlink creation; add a reportStarted() invocation (with the same banner message/context used for parent symlinks) immediately before creating the subdep symlink so missing/incorrect subdep symlinks also trigger the banner, or if this was intentional add a clear inline comment referencing symlinkDir and reportStarted to document the asymmetry—update code around symlinkDir, linkPath, and opts.parentNodeModulesDir accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@installing/env-installer/src/installConfigDeps.ts`:
- Around line 310-312: The subdependency symlink creation using symlinkDir for
subdeps (where linkPath = path.join(opts.parentNodeModulesDir, subdep.name))
currently doesn't call reportStarted(), unlike the parent symlink branch that
calls reportStarted() around parent symlink creation; add a reportStarted()
invocation (with the same banner message/context used for parent symlinks)
immediately before creating the subdep symlink so missing/incorrect subdep
symlinks also trigger the banner, or if this was intentional add a clear inline
comment referencing symlinkDir and reportStarted to document the
asymmetry—update code around symlinkDir, linkPath, and opts.parentNodeModulesDir
accordingly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 3c6384e9-de7f-4856-9099-6fdb44a7e8eb
📒 Files selected for processing (2)
.changeset/quiet-config-deps.mdinstalling/env-installer/src/installConfigDeps.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Agent
- GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Follow Standard Style with trailing commas, prefer functions over classes, declare functions after they are used (rely on hoisting), and use a single options object for functions with more than two or three arguments
Sort imports in three groups: standard libraries, external dependencies (alphabetically), then relative imports
Write code that explains itself through clear naming and types — do not write comments that merely restate what the code already says; use comments only for non-obvious reasons, hidden invariants, or workarounds
Files:
installing/env-installer/src/installConfigDeps.ts
🧠 Learnings (1)
📚 Learning: 2026-05-14T09:04:00.133Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11622
File: resolving/npm-resolver/test/publishedBy.test.ts:350-354
Timestamp: 2026-05-14T09:04:00.133Z
Learning: In the pnpm/pnpm repository, ESLint is the authoritative style linter. Do not raise review findings for missing trailing commas in multiline function calls (e.g., `fs.writeFileSync(...)`) when this repo’s ESLint configuration does not report them and lint passes. Prefer deferring to the ESLint results for this specific trailing-comma rule rather than enforcing it manually in code review.
Applied to files:
installing/env-installer/src/installConfigDeps.ts
🔇 Additional comments (8)
.changeset/quiet-config-deps.md (1)
1-7: LGTM!installing/env-installer/src/installConfigDeps.ts (7)
43-48: LGTM!
52-52: LGTM!
81-81: LGTM!
108-108: LGTM!Also applies to: 114-114
254-254: LGTM!
283-287: LGTM!
294-294: LGTM!
…hen work happens Captures `streamParser` events around `resolveAndInstallConfigDeps` to verify the lazy emission introduced in the previous commit: - fresh install emits both `started` and `done`, - a follow-up no-op install emits neither, - removing a config dep still emits `started` (orphan cleanup work). --- Written by an agent (Claude Code, claude-opus-4-7).
`streamParser` is a `split2` Transform stream that buffers writes until the first 'data' listener attaches and then drains the whole buffer into it. Subscribing per-test made the new install-config-deps test capture events from every earlier test in the file. Move the subscription to module load and have each test drain the accumulated events around its own call. Also drop the "removal" assertion: `resolveAndInstallConfigDeps` does not prune entries that disappear from the configDeps argument (lockfile pruning happens at a higher layer), so the scenario it claimed to test never actually fired the orphan-cleanup path. --- Written by an agent (Claude Code, claude-opus-4-7).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
installing/env-installer/test/resolveAndInstallConfigDeps.test.ts:265
- The new
reportStarted()gating has several distinct "work" paths (e.g. orphan cleanup when a config dep is removed, and orphan optional-subdep sibling cleanup). This test only covers fresh install (started+done) and a fully no-op rerun (no events). Adding an assertion for the removal/cleanup path (expecting astartedevent when a previously-installed config dep is removed) would help prevent regressions in the exact behavior this PR is changing.
test('emits installing-config-deps events only when work is needed', async () => {
prepareEmpty()
const opts = createOpts()
takeConfigDepEvents()
await resolveAndInstallConfigDeps({
'@pnpm.e2e/foo': '100.0.0',
}, opts)
const firstRunEvents = takeConfigDepEvents()
expect(firstRunEvents.map(e => e.status)).toEqual(['started', 'done'])
expect(firstRunEvents.find(e => e.status === 'done')?.deps).toEqual([
{ name: '@pnpm.e2e/foo', version: '100.0.0' },
])
await resolveAndInstallConfigDeps({
'@pnpm.e2e/foo': '100.0.0',
}, opts)
const secondRunEvents = takeConfigDepEvents()
expect(secondRunEvents).toStrictEqual([])
})
…relinking If a config dep's optional subdep is already cached in the global virtual store but the sibling symlink under the parent's node_modules is missing or points at a stale target, symlinkDir() does real work without reportStarted ever firing. Check whether the link already points at the expected target and only fire reportStarted + symlinkDir when it doesn't, mirroring the parentSymlinkAlreadyCorrect path. Also clean up the test-level streamParser listener in afterAll so the subscription doesn't outlive the test file. --- Written by an agent (Claude Code, claude-opus-4-7).
Summary
The default reporter printed
Installing config dependencies...every timepnpm installran with config dependencies, even when everything was already cached and correctly symlinked.In
installing/env-installer/src/installConfigDeps.tstheinstalling-config-deps'started'event was emitted unconditionally inside the per-depPromise.all, before any of the existence/symlink checks. The companion'done'event was already gated oninstalledConfigDeps.length, so an idempotent run printedInstalling...with no follow-upInstalled:line — the worst of both worlds.Fix
reportStarted()closure that emits the started event at most once perinstallConfigDepscall.reportStarted()only at the sites that do real fs work:parentSymlinkAlreadyCorrectshort-circuit),symlinkDircalls and theparentSymlinkAlreadyCorrectearly return no longer trigger the banner, so a fully no-op run is silent.Test plan
pnpm installin a workspace with config deps — first run printsInstalling config dependencies...andInstalled config dependencies: ...pnpm installin the same workspace — neither line is printedpnpm-workspace.yamland re-run — banner prints (orphan cleanup)Written by an agent (Claude Code, claude-opus-4-7).
Summary by CodeRabbit
Bug Fixes
Tests