Skip to content

deps: migrate pnpm internals to v12#10508

Open
zkochan wants to merge 3 commits into
teambit:masterfrom
zkochan:pnpm11-fresh
Open

deps: migrate pnpm internals to v12#10508
zkochan wants to merge 3 commits into
teambit:masterfrom
zkochan:pnpm11-fresh

Conversation

@zkochan

@zkochan zkochan commented Jul 20, 2026

Copy link
Copy Markdown
Member

Summary

  • Migrate Bit's pnpm integration to @pnpm/napi@12.0.0-alpha.17 and the pnpm v12 package split, including the new @pnpm/<domain>.<leaf> helper packages.
  • Add a small CJS-to-ESM loader for pnpm helper modules that are now ESM-only, and update config/auth/proxy/network handling to @pnpm/config.reader and typed config fields.
  • Port the pnpm install/resolve/rebuild/pack paths to the N-API surface, preserving Bit lockfile metadata across installs and adapting read-package hooks, rebuild behavior, script policies, and package-manager outputs.
  • Update dependency graph and lockfile conversion for the pnpm v12 lockfile shape: lazy-load ESM lockfile helpers, preserve valid workspace component package entries, scrub only unresolved unpublished workspace snaps, prune unreachable lockfile entries, and normalize merged peer-provider graphs.
  • Fix pnpm/root-component integration issues found during CI: app root manifests now include only the app's workspace dependency closure, injected absolute root-component paths are linked/written correctly, and compiled side effects are copied to all injected/root component dist dirs.
  • Keep install-time unpublished snap errors actionable under pnpm N-API resolver errors, including the new ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER path.
  • Fix CI PR lane branch detection for CircleCI/GitHub PR refs, including pull/<id>/merge, bounded GitHub API lookup, and regression coverage for the branch-ref matcher.
  • Harden CircleCI/BVM Bit CLI bundle publishing for pnpm N-API: copy packages into the release tarballs, verify target @pnpm/napi.* native binaries for linux/macos/windows bundles, and rebuild second-arch bundles from a clean install directory.
  • Refresh e2e/unit coverage and expectations for pnpm v12 lockfiles, deps-graph restore behavior, hoisting, package-manager config, tsconfig/env isolation, and review-comment regressions.

Test plan

  • bit compile teambit.dependencies/pnpm teambit.dependencies/dependency-resolver teambit.git/ci
  • bit test scopes/dependencies/pnpm/lynx.spec.ts scopes/dependencies/pnpm/lockfile-deps-graph-converter.spec.ts scopes/dependencies/dependency-resolver/dependency-resolver.main.runtime.spec.ts — 34/34 passed
  • bit test scopes/git/ci/pull-request-ref.spec.ts — 2/2 passed
  • npm run mocha-circleci -- e2e/harmony/deps-graph.e2e.ts --grep "two components with different peer dependencies( should| importing)" — 7 passing
  • npm run mocha-circleci -- e2e/harmony/dependencies/never-built-dependencies.e2e.ts --grep "using pnpm" — 1 passing
  • npm run mocha-circleci -- e2e/harmony/ci-commands.e2e.ts --grep "bit ci pr workflow" — 6 passing
  • npm run mocha-circleci -- e2e/harmony/root-components.e2e.ts --grep "pnpm hoisted linker" — 12 passing, 2 pending
  • Qodo review threads answered and resolved after fixes
  • Throwaway pnpm@10.14.0 hoisted/copy install of @pnpm/napi@12.0.0-alpha.17 --os=linux --cpu=arm64 verified the target pnpm-napi.node binary
  • .circleci/config.yml parsed with the repo yaml package
  • npm run lint-circle
  • bit test scopes/dependencies/pnpm/lynx.spec.ts scopes/dependencies/dependency-resolver/dependency-resolver.main.runtime.spec.ts — 13/13 passed after lint-only fixes
  • Broader e2e suite
  • CI green

Replaces #10293.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Migrate Bit’s pnpm integration to pnpm v12 (N-API) with ESM shims

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Migrate Bit’s pnpm integration to pnpm v12 N-API and renamed @pnpm/* modules.
• Add ESM loading shims and lockfile/graph pruning to keep installs deterministic.
• Update e2e expectations, CI timeouts, and Node/Babel config for the new runtime.
Diagram

graph TD
  A["InstallMain"] --> B["PnpmPackageManager"] --> C["pnpm/lynx.ts"] --> D["@pnpm/napi"]
  B --> E["load-pnpm-esm.cjs"] --> F["pnpm ESM modules"]
  B --> G["lockfile-deps-graph-converter"] --> H["DependenciesGraph"] --> I[("pnpm-lock.yaml")]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Stay on pnpm v11 TypeScript engine (@pnpm/core/mutateModules)
  • ➕ More feature-complete parity (peer issues, rebuild selectors) without N-API gaps
  • ➕ Fewer ESM interop shims and fewer runtime constraints
  • ➖ Blocks upgrade to newer pnpm internals and naming split
  • ➖ Continues reliance on deprecated/reshuffled internals and older dependency graph APIs
2. Wrap pnpm CLI (shell out) instead of using internal APIs
  • ➕ Avoids tight coupling to internal package renames and TS/N-API signatures
  • ➕ ESM/CJS boundary becomes Node’s problem, not Bit’s
  • ➖ Harder to integrate with Bit’s lockfile graph conversion and custom hooks
  • ➖ Weaker performance/telemetry control; brittle output parsing
3. Centralize ESM bridging behind a single internal pnpm adapter package
  • ➕ Keeps ESM-only dynamic imports and structural typing in one place
  • ➕ Reduces repeated loadPnpmEsm/loadLockfileFs patterns across modules
  • ➖ Extra refactor cost in this already-large migration
  • ➖ May delay landing the pnpm v12 upgrade

Recommendation: The chosen direction (move to @pnpm/napi and selectively bridge ESM-only helpers) is the right long-term alignment with pnpm v12. The main follow-up to consider is consolidating ESM loading and structural typings into one adapter module to reduce duplication (e.g., repeated loadPnpmEsm/loadLockfileFs patterns) once the migration is stable. Avoid switching to CLI wrapping unless internal API churn becomes unmanageable, since Bit needs deep integration (hooks, graph/lockfile manipulation, and install-time policies).

Files changed (37) +1835 / -5728

Enhancement (11) +741 / -371
npm-ci-registry.tsLazy-load pnpm fetch via ESM-friendly dynamic import +1/-1

Lazy-load pnpm fetch via ESM-friendly dynamic import

• Removes static import of @pnpm/fetch and replaces it with a runtime dynamic import of @pnpm/network.fetch to align with pnpm package renames/ESM constraints.

components/legacy/e2e-helper/npm-ci-registry.ts

dependency-resolver.main.runtime.tsReplace pnpm bare-spec parser with local version-spec validation +10/-9

Replace pnpm bare-spec parser with local version-spec validation

• Drops @pnpm/npm-resolver’s parseBareSpecifier dependency and implements a local validator supporting semver/ranges, workspace/file/link/git protocols, registry tarball URLs, npm aliasing, and simple tags.

scopes/dependencies/dependency-resolver/dependency-resolver.main.runtime.ts

get-proxy-config.tsAdapt proxy config lookup to config.reader authConfig/noProxy fields +5/-2

Adapt proxy config lookup to config.reader authConfig/noProxy fields

• Moves Config typing to @pnpm/config.reader and resolves noProxy from typed fields plus compatible fallbacks across authConfig/rawConfig keys.

scopes/dependencies/pnpm/get-proxy-config.ts

get-registries.tsUse config.authConfig and typed default registry for credentials +12/-7

Use config.authConfig and typed default registry for credentials

• Migrates from rawConfig to authConfig for credentials lookup and fixes original-auth selection using the typed default registry URI instead of reading registry from ini.

scopes/dependencies/pnpm/get-registries.ts

load-pnpm-esm.cjsAdd CJS shim to load pnpm ESM-only helper modules +19/-0

Add CJS shim to load pnpm ESM-only helper modules

• Introduces a native CommonJS loader that dynamically imports ESM-only pnpm packages and caches the result for reuse across the pnpm integration.

scopes/dependencies/pnpm/load-pnpm-esm.cjs

lockfile-deps-graph-converter.tsLoad ESM pnpm helpers lazily and prune unreachable lockfile entries +100/-8

Load ESM pnpm helpers lazily and prune unreachable lockfile entries

• Replaces direct imports with an init()-based dynamic loader for @pnpm/deps.path and @pnpm/lockfile.fs. Fixes resolve options to use rootDir, hardens integrity handling, scrubs failed workspace-component packages, and drops unreachable packages/snapshots (including Bit build metadata) from generated lockfiles.

scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts

lynx.tsPort pnpm integration to @pnpm/napi (install/resolve/rebuild/pack) +419/-294

Port pnpm integration to @pnpm/napi (install/resolve/rebuild/pack)

• Replaces the old @pnpm/core/client/store-controller flow with @pnpm/napi install/resolve/rebuild APIs. Adds authHeaderByUri derivation, proxy/network mapping, read-package hook composition, lockfile bit-block preservation across rewrites, update-all lockfile handling, and compatibility for build-script policies via allowBuilds/dangerouslyAllowAllBuilds.

scopes/dependencies/pnpm/lynx.ts

pnpm-prune-modules.tsLazy-load readCurrentLockfile from ESM @pnpm/lockfile.fs +16/-1

Lazy-load readCurrentLockfile from ESM @pnpm/lockfile.fs

• Introduces a local loader to dynamically import @pnpm/lockfile.fs and then uses readCurrentLockfile for pruning the virtual store directory.

scopes/dependencies/pnpm/pnpm-prune-modules.ts

pnpm.package-manager.tsUpdate pnpm package manager to new module names + lockfile pruning +149/-41

Update pnpm package manager to new module names + lockfile pruning

• Switches to @pnpm/config.reader, @pnpm/installing.modules-yaml, and new deps-inspection packages, plus adds an ESM loader for pnpm helpers. Initializes the lockfile graph converter, resolves hoist-pattern precedence, reads network settings from typed config fields, and prunes unreachable lockfile entries after merging graph-derived lockfile subsets.

scopes/dependencies/pnpm/pnpm.package-manager.ts

read-config.tsRead pnpm config via config.reader with explicit workspaceDir +4/-1

Read pnpm config via config.reader with explicit workspaceDir

• Migrates getConfig import to @pnpm/config.reader and passes workspaceDir so pnpm resolves workspace-level configuration consistently.

scopes/dependencies/pnpm/read-config.ts

packer.tsUse pnpm N-API pack instead of plugin-commands-publishing +6/-7

Use pnpm N-API pack instead of plugin-commands-publishing

• Replaces @pnpm/plugin-commands-publishing pack call with @pnpm/napi pack and correctly resolves tarball paths when returned as absolute or relative.

scopes/pkg/pkg/packer.ts

Bug fix (6) +298 / -31
merge-files.tsAvoid rethrowing after printing merge diagnostics +0/-1

Avoid rethrowing after printing merge diagnostics

• Removes a rethrow in the error handler, likely to prevent duplicated failures after emitting formatted error details.

scopes/component/modules/merge-helper/merge-files.ts

pnpm-error-to-bit-error.tsHandle pnpm N-API errors structurally (not as PnpmError class) +15/-3

Handle pnpm N-API errors structurally (not as PnpmError class)

• Defines a PnpmErrorLike interface and updates conversion logic to rely on optional fields (code/hint/prefix/pkgsStack) rather than @pnpm/error’s class types.

scopes/dependencies/pnpm/pnpm-error-to-bit-error.ts

ci.main.runtime.tsImprove branch detection for CircleCI PR ref checkouts +39/-0

Improve branch detection for CircleCI PR ref checkouts

• Avoids treating pull/* refs as real branch names and, when necessary, resolves the PR head ref via the GitHub API using CIRCLE_PULL_REQUEST(S). This keeps lane/cleanup logic correct in CircleCI workflows and e2e scenarios.

scopes/git/ci/ci.main.runtime.ts

dependencies-graph.tsNormalize merged graphs: peer providers + unreachable pruning +234/-19

Normalize merged graphs: peer providers + unreachable pruning

• Strengthens graph merge logic by merging root-edge neighbours deterministically, normalizing peer provider versions to the highest compatible, and pruning unreachable nodes/packages from the root.

scopes/scope/objects/models/dependencies-graph.ts

schema-extractor-context.tsAdjust unresolved-identifier handling path +0/-1

Adjust unresolved-identifier handling path

• Removes an early return constructing a TypeRefSchema in an unresolved identifier branch, likely allowing subsequent logic to handle the case consistently.

scopes/typescript/typescript/schema-extractor-context.ts

install.main.runtime.tsTreat N-API SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER as unpublished-snap candidate +10/-7

Treat N-API SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER as unpublished-snap candidate

• Expands unpublished-snap dependency error enrichment to recognize pnpm N-API’s SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER code in addition to the legacy NO_MATCHING_VERSION path.

scopes/workspace/install/install.main.runtime.ts

Refactor (1) +0 / -2
workspace-aspects-loader.tsRemove unreachable throw after returning scope aspects +0/-2

Remove unreachable throw after returning scope aspects

• Deletes a dead throw statement after an early return in the error-handling path.

scopes/workspace/workspace/workspace-aspects-loader.ts

Tests (10) +172 / -59
dependency-resolver.e2e.tsUpdate modules manifest + lockfile assertions for new pnpm internals +17/-8

Update modules manifest + lockfile assertions for new pnpm internals

• Switches modules manifest access to @pnpm/installing.modules-yaml via dynamic import and replaces .pnpm folder assertions with pnpm-lock.yaml snapshot/package checks. Also forces minimumReleaseAge=0 to stabilize registry resolution in tests.

e2e/harmony/dependency-resolver.e2e.ts

deps-graph-reimport.e2e.tsStabilize dependency resolver behavior under pnpm v12 +6/-8

Stabilize dependency resolver behavior under pnpm v12

• Sets dependency-resolver minimumReleaseAge=0 in multiple suites and removes an assertion documenting a prior lockfile merge limitation that no longer applies (or is no longer stable).

e2e/harmony/deps-graph-reimport.e2e.ts

deps-graph.e2e.tsStabilize deps-graph e2e with minimumReleaseAge override +3/-0

Stabilize deps-graph e2e with minimumReleaseAge override

• Adds dependency-resolver minimumReleaseAge=0 to reduce flaky behavior when resolving latest versions during tests.

e2e/harmony/deps-graph.e2e.ts

install.e2e.tsAssert updates via pnpm-lock.yaml instead of virtual-store dirs +11/-2

Assert updates via pnpm-lock.yaml instead of virtual-store dirs

• Adds YAML parsing and validates updated subdependencies through lockfile packages/snapshots. Also forces minimumReleaseAge=0 for deterministic dist-tag installs.

e2e/harmony/install.e2e.ts

pkg-manager-config.e2e.tsValidate capsule installs read workspace pnpm config (pnpm-workspace.yaml) +20/-14

Validate capsule installs read workspace pnpm config (pnpm-workspace.yaml)

• Moves modules manifest reading to @pnpm/installing.modules-yaml (dynamic import) and updates the test to assert pnpm-workspace.yaml hoistPattern is applied in capsules (instead of .npmrc). Adjusts workspace flags to keep aspect/env resolution stable.

e2e/harmony/pkg-manager-config.e2e.ts

pnpm-default-hoisting.e2e.tsUse installing.modules-yaml API via dynamic import +6/-2

Use installing.modules-yaml API via dynamic import

• Replaces @pnpm/modules-yaml with @pnpm/installing.modules-yaml and wraps readModulesManifest in an async dynamic import helper for ESM compatibility.

e2e/harmony/pnpm-default-hoisting.e2e.ts

tsconfig-env-mismatch.e2e.tsStabilize tsconfig mismatch test setup and assertions +3/-8

Stabilize tsconfig mismatch test setup and assertions

• Installs required TypeScript/compiler tooling earlier in the suite, removes redundant install during link/build, and simplifies the build assertion to expect no throw.

e2e/harmony/tsconfig-env-mismatch.e2e.ts

lockfile-deps-graph-converter.spec.tsInitialize converter ESM deps and add peer-provider merge coverage +84/-9

Initialize converter ESM deps and add peer-provider merge coverage

• Adds an init() call before tests (to load ESM-only pnpm helpers) and introduces a test ensuring merged graphs pick the highest compatible peer provider version when producing a lockfile.

scopes/dependencies/pnpm/lockfile-deps-graph-converter.spec.ts

pnpm-error-to-bit-error.spec.tsAvoid importing ESM @pnpm/error in tests +15/-5

Avoid importing ESM @pnpm/error in tests

• Replaces PnpmError construction with a structural error factory to keep mocha/Babel from pulling ESM-only @pnpm/error into the test runtime.

scopes/dependencies/pnpm/pnpm-error-to-bit-error.spec.ts

schema.spec.tsStabilize chai-subset usage via typed helper +7/-3

Stabilize chai-subset usage via typed helper

• Introduces a helper wrapper for containSubset to avoid typing/chain issues and updates schema subset assertions to use it.

scopes/semantics/schema/schema.spec.ts

Other (9) +624 / -5265
config.ymlIncrease no-output timeout for split e2e runs +1/-0

Increase no-output timeout for split e2e runs

• Adds a 50-minute no_output_timeout to the e2e split-test command step to prevent CircleCI from killing long silent runs.

.circleci/config.yml

babel.config.jsExclude dynamic-import transform to preserve native import() +1/-0

Exclude dynamic-import transform to preserve native import()

• Configures Babel preset-env to exclude @babel/plugin-transform-dynamic-import so dynamic import remains intact for ESM-only pnpm modules.

babel.config.js

package.jsonBump required Node engine to 22.13+ +1/-1

Bump required Node engine to 22.13+

• Updates the project’s engines.node requirement to >=22.13.0, aligning runtime expectations with pnpm v12 / ESM/N-API usage.

package.json

pnpm-lock.yamlRefresh lockfile for pnpm v12 internal dependency set +588/-5236

Refresh lockfile for pnpm v12 internal dependency set

• Updates the workspace lockfile to reflect the renamed @pnpm/* packages, new versions, and N-API adoption.

pnpm-lock.yaml

package-manager.tsSwitch peer-issues typing to pnpm N-API +1/-1

Switch peer-issues typing to pnpm N-API

• Updates PeerDependencyIssuesByProjects import to come from @pnpm/napi instead of @pnpm/core.

scopes/dependencies/dependency-resolver/package-manager.ts

yarn.package-manager.tsSwitch parseOverrides import to config.parse-overrides +1/-1

Switch parseOverrides import to config.parse-overrides

• Updates the pnpm overrides parser dependency to the new module split used by recent pnpm versions.

scopes/dependencies/yarn/yarn.package-manager.ts

babel-config.tsPreserve native dynamic import in Harmony Babel config +1/-0

Preserve native dynamic import in Harmony Babel config

• Matches root Babel behavior by excluding @babel/plugin-transform-dynamic-import to keep import() available for ESM-only pnpm modules at runtime.

scopes/harmony/aspect/babel/babel-config.ts

e2e-test-timings.jsonUpdate e2e runtime manifest for scheduling/splitting +3/-1

Update e2e runtime manifest for scheduling/splitting

• Bumps expected runtime for deps-graph.e2e.ts and adds timings for newly tracked tests to keep CircleCI splitting accurate.

scripts/e2e-test-timings.json

workspace.jsoncRename and bump @pnpm/* internal deps; add overrides for mirror gaps +27/-25

Rename and bump @pnpm/* internal deps; add overrides for mirror gaps

• Migrates pnpm internal dependencies to the v11+ split naming and introduces @pnpm/napi (v12 alpha). Adds resolution overrides to pin packages to versions available in bit.cloud’s registry mirror and updates related engine settings.

workspace.jsonc

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 20, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (8) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Null refToRelative neighbor id 🐞 Bug ≡ Correctness
Description
importerDepsToNeighbours() uses a non-null assertion on dp.refToRelative(version, name), but the
same module already treats refToRelative() as nullable; when it returns null (e.g., for
local/link-style refs), the graph is built with an invalid neighbour id and later graph processing
can fail.
Code

scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[1]

             bareSpecifier: pkgToResolve.version,
Evidence
The converter already assumes dp.refToRelative() can return null (it checks before pushing).
Meanwhile, the graph→lockfile path emits importer dependencies with link: versions, and the spec
asserts those exist (e.g. bar: { version: 'link:../bar' ... }). Feeding such a lockfile back into
importerDepsToNeighbours() will call dp.refToRelative('link:../bar', 'bar') and the current !
can yield an invalid neighbour id.

scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[73-83]
scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[201-216]
scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[350-353]
scopes/dependencies/pnpm/lockfile-deps-graph-converter.spec.ts[228-275]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`importerDepsToNeighbours()` currently does `dp.refToRelative(version, name)!` and blindly pushes the resulting `id` into the neighbours list. However, the same file already handles `dp.refToRelative(...)` as nullable elsewhere, so this path can introduce `null/undefined` ids into the dependency graph (or throw later when consumers assume `id` is a string).
This is especially risky because the lockfile importer entries can legitimately contain non-registry refs (e.g. `link:../bar`), which are not guaranteed to be convertible to a pnpm depPath.
### Issue Context
- `extractDependenciesFromSnapshot()` already guards `dp.refToRelative(...)` with a null-check, proving the API is nullable.
- `convertGraphToLockfile()` explicitly writes importer entries with `link:` versions.
- The converter spec asserts that such `link:` entries exist in the generated lockfile importers.
### Fix Focus Areas
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[73-84]
### Suggested fix
1. Replace the non-null assertion with a null-check:
 - `const id = dp.refToRelative(version, name);`
 - `if (!id) continue;` (or otherwise handle link/file refs explicitly if they must be represented)
2. Add/adjust a unit test that covers converting a lockfile whose importer dependency includes a `link:` version (or any other ref that makes `refToRelative` return null), asserting the converter does not crash and produces a sane graph.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Broken heredoc invocation 🐞 Bug ≡ Correctness
Description
The verify_pnpm_napi_bundle step runs node \<<'NODE', which escapes the heredoc operator so the
JS snippet may not be redirected into Node, breaking or stalling the CI verification command. This
affects every job that invokes the new bundle verification command (linux/macos/windows).
Code

.circleci/config.yml[R160-163]

+          command: |
+            cd <<parameters.directory>>
+            PNPM_NAPI_PACKAGE="<<parameters.package>>" node \<<'NODE'
+            const fs = require('fs');
Evidence
The CircleCI command block shows node \<<'NODE' before the inline JS. Escaping the heredoc
operator prevents the script body from being redirected into Node, so the verification step cannot
execute reliably.

.circleci/config.yml[160-166]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The CircleCI step `verify_pnpm_napi_bundle` uses `node \<<'NODE'`. The backslash escapes the heredoc operator (`<<`), so the shell may not treat it as a heredoc redirection and Node may not receive the embedded JS program.
## Issue Context
This command is used to verify that the platform-specific `@pnpm/napi.*` package includes `pnpm-napi.node` inside the Bit CLI bundle.
## Fix Focus Areas
- .circleci/config.yml[160-163]
## How to Fix
- Change:
- `PNPM_NAPI_PACKAGE="<<parameters.package>>" node \<<'NODE'`
- To:
- `PNPM_NAPI_PACKAGE="<<parameters.package>>" node <<'NODE'`
If CircleCI config processing conflicts with raw `<<`, alternatively avoid heredocs entirely (e.g., move the JS snippet into a checked-in script file and run `node path/to/script.js`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. git+ssh policy rejected 🐞 Bug ≡ Correctness
Description
DependencyResolverMain.validateAspectData() validates policy versions using
isValidVersionSpecifier(), but isValidVersionSpecifier() does not accept the pnpm-supported
git+ssh:// scheme. This will reject valid dependency policy entries (e.g. in
workspace.jsonc/env.jsonc) and fail aspect-data validation for those components/workspaces.
Code

scopes/dependencies/dependency-resolver/dependency-resolver.main.runtime.ts[R1475-1485]

isValidVersionSpecifier(spec: string): boolean {
-    return (
-      parseBareSpecifier(
-        spec,
-        'pkgname', // This argument is the package but we don't need it
-        'latest',
-        'https://registry.npmjs.org/'
-      ) != null
-    );
+    const trimmedSpec = spec.trim();
+    if (!trimmedSpec) return false;
+    if (semver.valid(trimmedSpec) || semver.validRange(trimmedSpec)) return true;
+    if (/^(?:workspace|file|link|git|git\+https?|ssh):/.test(trimmedSpec)) return true;
+    if (/^https?:\/\/registry\.npmjs\.org\/.+\/-.+\.tgz(?:[#?].*)?$/.test(trimmedSpec)) return true;
+    if (trimmedSpec.startsWith('npm:')) {
+      const versionStart = trimmedSpec.lastIndexOf('@');
+      return versionStart > 'npm:'.length && this.isValidVersionSpecifier(trimmedSpec.slice(versionStart + 1));
+    }
+    return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(trimmedSpec);
Evidence
The policy-validation path calls isValidVersionSpecifier() (no extra prefix allowances), and
isValidVersionSpecifier()’s protocol whitelist omits git+ssh: so git+ssh://... will not match
any accepted branch and will be treated as invalid.

scopes/dependencies/dependency-resolver/dependency-resolver.main.runtime.ts[1432-1459]
scopes/dependencies/dependency-resolver/dependency-resolver.main.runtime.ts[1475-1485]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DependencyResolverMain.isValidVersionSpecifier()` is used to validate dependency-policy versions, but its protocol whitelist regex does not include `git+ssh:`. pnpm supports `git+ssh://...` and the same file already treats `git+ssh://` as an allowed dependency prefix elsewhere, so policy validation becomes inconsistent and rejects valid configs.
## Issue Context
- `validateAspectData()` validates `data.policy` versions only through `isValidVersionSpecifier()` (plus `+`/`-`), so missing protocol support becomes a hard validation failure.
## Fix Focus Areas
- scopes/dependencies/dependency-resolver/dependency-resolver.main.runtime.ts[1432-1459]
- scopes/dependencies/dependency-resolver/dependency-resolver.main.runtime.ts[1475-1485]
## Proposed fix
- Expand the protocol regex to include `git\+ssh:` (and preferably keep parity with pnpm-supported git URL forms).
- Add/extend a unit test (if present for validation) that asserts `git+ssh://...` policy versions are accepted.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
4. Allow-all scripts flag lost 🐞 Bug ≡ Correctness
Description
resolveScriptPolicies() never sets dangerouslyAllowAllBuilds, so when callers pass
dangerouslyAllowAllScripts the intended "allow all builds" behavior is not enabled and script
execution is evaluated under the ordinary allowBuilds map instead. This can break installs that
require lifecycle/build scripts (e.g. native modules) while silently ignoring the user’s
configuration.
Code

scopes/dependencies/pnpm/lynx.ts[R549-563]

+  neverBuiltDependencies,
+}: ScriptPolicyConfig): { allowBuilds: Record<string, boolean | string>; dangerouslyAllowAllBuilds?: boolean } {
+  const allowBuilds: Record<string, boolean | string> = {};
if (dangerouslyAllowAllScripts) {
-    if (resolvedNeverBuilt == null) {
-      // If neverBuiltDependencies is not explicitly set, use a default list
-      // we tell pnpm to allow all scripts to be executed, except the packages listed below.
-      resolvedNeverBuilt = ['core-js'];
+    // pnpm v11's createAllowBuildFunction ignores allowBuilds when dangerouslyAllowAllBuilds is
+    // set, so emit allowBuilds with just the deny entries whenever a deny list exists.
+    if (!neverBuiltDependencies?.length) {
+      allowBuilds['core-js'] = false;
+      return { allowBuilds };
}
-  } else {
-    onlyBuiltDependencies = [];
-    ignoredBuiltDependencies = [];
-    for (const [packageDescriptor, allowedScript] of Object.entries(allowScripts ?? {})) {
-      switch (allowedScript) {
-        case true:
-          onlyBuiltDependencies.push(packageDescriptor);
-          break;
-        case false:
-          ignoredBuiltDependencies.push(packageDescriptor);
-          break;
-        default:
-          // Ignore any non-boolean values. String values are placeholders that the user
-          // should replace with booleans.
-          // pnpm will print a warning about these during installation.
-          break;
-      }
+    for (const pkg of neverBuiltDependencies) {
+      allowBuilds[pkg] = false;
}
-    for (const trustedPkgName of TRUSTED_PACKAGE_NAMES) {
-      if (allowScripts?.[trustedPkgName] == null) {
-        onlyBuiltDependencies.push(trustedPkgName);
-      }
+    return { allowBuilds };
+  }
Evidence
The install path explicitly forwards dangerouslyAllowAllBuilds to @pnpm/napi, but the policy
builder never sets it even when dangerouslyAllowAllScripts is provided by the caller, so the
allow-all behavior cannot take effect.

scopes/dependencies/pnpm/lynx.ts[368-399]
scopes/dependencies/pnpm/lynx.ts[540-584]
scopes/dependencies/pnpm/pnpm.package-manager.ts[193-215]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`dangerouslyAllowAllScripts` is accepted by Bit’s pnpm install options, but the pnpm v12/N-API install path never translates it into the new engine flag (`dangerouslyAllowAllBuilds`). As a result, callers expecting “allow all build scripts” don’t get that behavior.
## Issue Context
- `install()` forwards `scriptPolicies.dangerouslyAllowAllBuilds` into `nodeApi.install(...)`, but `resolveScriptPolicies()` always returns only `{ allowBuilds }`.
- `PnpmPackageManager` passes `dangerouslyAllowAllScripts` down to `lynx.install()`, so this is a user-visible regression.
## Fix Focus Areas
- scopes/dependencies/pnpm/lynx.ts[540-584]
- scopes/dependencies/pnpm/lynx.ts[368-401]
## Suggested fix
- In `resolveScriptPolicies()`, when `dangerouslyAllowAllScripts` is true, return `{ allowBuilds, dangerouslyAllowAllBuilds: true }`.
- Preserve the existing deny behavior for `neverBuiltDependencies` (and the default `core-js` deny) by keeping those entries in `allowBuilds`.
- Add/adjust a unit/e2e test that sets `dangerouslyAllowAllScripts: true` and asserts the install engine receives `dangerouslyAllowAllBuilds: true` (or that a known script-required package successfully builds).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Over-scrubs workspace lockfile 🐞 Bug ≡ Correctness
Description
convertGraphToLockfile() pre-populates failedWorkspaceComponentPkgs with every workspace
component pkgId, so scrubPkgsFromLockfile() will always delete those packages and remove
snapshot/importer references even when resolutions are valid, producing an incomplete/incorrect
generated lockfile.
Code

scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[R369-373]

const pkgsToResolve = getPkgsToResolve(lockfile, manifests);
const failedWorkspaceComponentPkgs = new Set<string>();
+  for (const pkgId of getWorkspaceComponentPkgIds(lockfile, graph, manifests)) {
+    failedWorkspaceComponentPkgs.add(pkgId);
+  }
Evidence
The function seeds failedWorkspaceComponentPkgs from getWorkspaceComponentPkgIds() and later
scrubs when the set is non-empty. scrubPkgsFromLockfile() deletes entries from lockfile.packages
and removes matching dependencies from snapshots and importers, so seeding the set with all
workspace component pkgIds removes valid data regardless of resolution success.

scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[369-442]
scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[640-682]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`convertGraphToLockfile()` currently seeds `failedWorkspaceComponentPkgs` with all workspace component pkgIds before any resolution/validation happens, and then scrubs them from the lockfile. This can remove legitimate component deps and their edges from the generated lockfile.
### Issue Context
The scrub set should only contain pkgIds that are actually invalid/unresolvable (e.g., unpublished snap versions) so pnpm can fall back to resolving from importer specifiers.
### Fix Focus Areas
- Remove unconditional seeding of `failedWorkspaceComponentPkgs` and instead:
- Keep a separate `workspaceComponentPkgIds` set (if needed for later checks), and
- Add to `failedWorkspaceComponentPkgs` only when a workspace component has missing/empty resolution after the resolve pass.
- Ensure `scrubPkgsFromLockfile()` is called only when there are *actual* failures.
#### Code locations
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[369-442]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

6. Wildcard direct deps merged wrong 🐞 Bug ≡ Correctness ⭐ New
Description
DependenciesGraph.merge() can treat two direct deps as the same when either specifier is '*' and
then only updates the id, leaving the wildcard specifier in place. convertGraphToLockfile() later
matches root direct deps by exact specifier, so merged graphs can fail to emit importer entries for
non-wildcard manifest specs, producing an incomplete lockfile.
Code

scopes/scope/objects/models/dependencies-graph.ts[R78-92]

  merge(graph: DependenciesGraph): void {
-    const directDependencies = graph.findRootEdge()?.neighbours;
-    if (directDependencies) {
+    const rootEdge = this.findRootEdge();
+    const incomingRootEdge = graph.findRootEdge();
+    const directDependencies = incomingRootEdge?.neighbours;
+    if (directDependencies && rootEdge) {
      for (const directDep of directDependencies) {
-        const existingDirectDeps = this.findRootEdge()?.neighbours;
-        if (existingDirectDeps) {
-          const existingDirectDep = existingDirectDeps.find(
-            ({ name, specifier }) => name === directDep.name && specifier === directDep.specifier
-          );
-          if (existingDirectDep == null) {
-            existingDirectDeps.push(directDep);
-          } else if (existingDirectDep.id !== directDep.id && nodeIdLessThan(existingDirectDep.id, directDep.id)) {
-            existingDirectDep.id = directDep.id;
-          }
+        const existingDirectDep = rootEdge.neighbours.find((existingDep) =>
+          isSameDirectDependency(existingDep, directDep)
+        );
+        if (existingDirectDep == null) {
+          rootEdge.neighbours.push(directDep);
+        } else if (existingDirectDep.id !== directDep.id && nodeIdLessThan(existingDirectDep.id, directDep.id)) {
+          existingDirectDep.id = directDep.id;
        }
      }
Evidence
The merge logic treats wildcard specifiers as matching any specifier but only updates the neighbour
id; later lockfile generation requires an exact specifier match (or pinned-version equality) to
populate importer dependencies, so retaining '*' can prevent importer entries from being emitted.

scopes/scope/objects/models/dependencies-graph.ts[78-102]
scopes/scope/objects/models/dependencies-graph.ts[260-268]
scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[339-364]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DependenciesGraph.merge()` merges two root-edge neighbours if `isSameDirectDependency()` returns true. The helper currently considers a wildcard specifier (`*`/empty/undefined) compatible with any specifier, but the merge only updates `existingDirectDep.id` and does not update `existingDirectDep.specifier`.

Downstream, `convertGraphToLockfile()` searches for a root-edge neighbour by `(directDep.name === name && (directDep.specifier === specifier || dp.removeSuffix(directDep.id) === `${name}@${specifier}`))`. If the merged neighbour retained `specifier='*'` while the manifest specifier is `^x.y.z`, the lookup can fail and the importer dependency entry will not be written.

## Issue Context
A minimal failure scenario:
- Existing root neighbour: `{ name: 'foo', specifier: '*', id: 'foo@1.0.0' }`
- Incoming root neighbour: `{ name: 'foo', specifier: '^2.0.0', id: 'foo@2.0.0' }`
After merge, neighbour can become `{ name: 'foo', specifier: '*', id: 'foo@2.0.0' }`.
Then `convertGraphToLockfile()` searching for manifest `foo: '^2.0.0'` won’t match `directDep.specifier === specifier`, and won’t match the `${name}@${specifier}` fallback either.

## Fix Focus Areas
- scopes/scope/objects/models/dependencies-graph.ts[78-102]
- scopes/scope/objects/models/dependencies-graph.ts[260-268]
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[339-364]

## Suggested fix
Option A (recommended): when merging two direct deps that match by wildcard, prefer the more specific specifier:
- If `existing.specifier` is wildcard and `incoming.specifier` is not, set `existing.specifier = incoming.specifier`.
- If both are non-wildcard but differ, keep both entries (don’t treat them as identical).

Option B: tighten `isSameDirectDependency()` so wildcard does not match a concrete specifier (only treat as same when both specifiers are equal or both wildcard).

Add a unit test that merges graphs with wildcard vs non-wildcard specifiers and asserts `convertGraphToLockfile()` writes the importer dependency entry.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. PR merge ref misdetected 🐞 Bug ☼ Reliability
Description
CiMain.getBranchName() uses isPullRequestRef() to avoid returning CircleCI PR refs, but
isPullRequestRef() does not match the common GitHub PR ref form pull//merge. In that case
getBranchName() may return the synthetic PR ref as the branch name, breaking lane naming and
branch-based cleanup logic.
Code

scopes/git/ci/ci.main.runtime.ts[R183-204]

  if (process.env.GITHUB_HEAD_REF) return process.env.GITHUB_HEAD_REF;
  const branch = await git.branch();
+      if (branch.current && !this.isPullRequestRef(branch.current)) return branch.current;
+
+      // CircleCI can check out GitHub PRs as refs like "pull/10293". In that case the real source
+      // branch is the stable lane name we also use for post-merge cleanup. Do not prefer these env
+      // vars over a normal git branch; e2e tests intentionally create local feature branches while
+      // running under CircleCI.
+      if (process.env.CIRCLE_BRANCH && !this.isPullRequestRef(process.env.CIRCLE_BRANCH)) return process.env.CIRCLE_BRANCH;
+      const circlePullRequestBranch = await this.getCirclePullRequestBranchName();
+      if (circlePullRequestBranch) return circlePullRequestBranch;
+
  return branch.current;
} catch (e: any) {
  throw new Error(`Unable to read branch: ${e.toString()}`);
}
}
+  private isPullRequestRef(branchName: string): boolean {
+    return /^pull\/\d+(?:\/head)?$/.test(branchName);
+  }
Evidence
getBranchName() returns branch.current unless isPullRequestRef() matches; isPullRequestRef() only
allows an optional /head, so /merge PR refs will slip through as “normal branches”.

scopes/git/ci/ci.main.runtime.ts[180-204]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`CiMain.isPullRequestRef()` only recognizes `pull/<id>` and `pull/<id>/head`. Some CI checkouts (including GitHub PR merge refs) use `pull/<id>/merge`, which will be misclassified as a normal branch and returned early by `getBranchName()`.
## Issue Context
`getBranchName()` prefers `git.branch().current` when it is not a PR ref; the intent in comments is explicitly to avoid returning these PR refs.
## Fix Focus Areas
- scopes/git/ci/ci.main.runtime.ts[180-204]
## Proposed fix
- Update the regex to include merge refs, e.g. `^pull\/\d+(?:\/(?:head|merge))?$`.
- (Optional) Add a small unit test for `isPullRequestRef()` covering `pull/123`, `pull/123/head`, and `pull/123/merge`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


8. Unbounded GitHub API fetch 🐞 Bug ☼ Reliability
Description
CiMain.getBranchName() may await getCirclePullRequestBranchName(), which calls the GitHub API
via fetch() without any timeout/abort, so branch resolution can be delayed indefinitely under
stalled network conditions.
Code

scopes/git/ci/ci.main.runtime.ts[R206-226]

+  private async getCirclePullRequestBranchName(): Promise<string | undefined> {
+    const pullRequestUrl = process.env.CIRCLE_PULL_REQUEST || process.env.CIRCLE_PULL_REQUESTS?.split(',')[0];
+    if (!pullRequestUrl) return undefined;
+
+    try {
+      const { pathname } = new URL(pullRequestUrl);
+      const [, owner, repo, pullSegment, pullNumber] = pathname.split('/');
+      if (!owner || !repo || pullSegment !== 'pull' || !pullNumber) return undefined;
+
+      const headers: Record<string, string> = {
+        Accept: 'application/vnd.github+json',
+        'X-GitHub-Api-Version': '2022-11-28',
+      };
+      if (process.env.GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
+
+      const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/pulls/${pullNumber}`, { headers });
+      if (!response.ok) return undefined;
+
+      const pullRequest = (await response.json()) as { head?: { ref?: string } };
+      return pullRequest.head?.ref;
+    } catch {
Evidence
The new method getCirclePullRequestBranchName() issues a plain fetch() to api.github.com and
is awaited from getBranchName() without any timeout/abort mechanism, so the overall
branch-resolution call is coupled to an unbounded network operation.

scopes/git/ci/ci.main.runtime.ts[180-228]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`getCirclePullRequestBranchName()` performs a GitHub API `fetch()` with no timeout/AbortSignal. In some network failure modes (stalled connection, proxy issues), this can delay `getBranchName()` for an unbounded time.
### Issue Context
This path runs when CircleCI checks out PR refs like `pull/<num>` and the code tries to discover the real source branch using the PR URL.
### Fix Focus Areas
- Add an `AbortController` with a short timeout (e.g., 3–10s) around the `fetch()`.
- On abort/timeout, return `undefined` (current behavior already falls back to `branch.current`).
- Optionally add a lightweight debug log when the lookup times out to aid CI diagnosis.
#### Code locations
- scopes/git/ci/ci.main.runtime.ts[180-228]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts Outdated
Comment thread scopes/git/ci/ci.main.runtime.ts
Comment thread scopes/dependencies/pnpm/lynx.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 4dc7b80

Comment thread scopes/git/ci/ci.main.runtime.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit ab13018

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 9068b8c

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 21, 2026

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

Comment thread .circleci/config.yml
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 3d3660f

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit aacaa7f

@zkochan
zkochan requested a review from GiladShoham July 21, 2026 09:01
@zkochan
zkochan enabled auto-merge (squash) July 21, 2026 09:01
Comment on lines 78 to 92
merge(graph: DependenciesGraph): void {
const directDependencies = graph.findRootEdge()?.neighbours;
if (directDependencies) {
const rootEdge = this.findRootEdge();
const incomingRootEdge = graph.findRootEdge();
const directDependencies = incomingRootEdge?.neighbours;
if (directDependencies && rootEdge) {
for (const directDep of directDependencies) {
const existingDirectDeps = this.findRootEdge()?.neighbours;
if (existingDirectDeps) {
const existingDirectDep = existingDirectDeps.find(
({ name, specifier }) => name === directDep.name && specifier === directDep.specifier
);
if (existingDirectDep == null) {
existingDirectDeps.push(directDep);
} else if (existingDirectDep.id !== directDep.id && nodeIdLessThan(existingDirectDep.id, directDep.id)) {
existingDirectDep.id = directDep.id;
}
const existingDirectDep = rootEdge.neighbours.find((existingDep) =>
isSameDirectDependency(existingDep, directDep)
);
if (existingDirectDep == null) {
rootEdge.neighbours.push(directDep);
} else if (existingDirectDep.id !== directDep.id && nodeIdLessThan(existingDirectDep.id, directDep.id)) {
existingDirectDep.id = directDep.id;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Wildcard direct deps merged wrong 🐞 Bug ≡ Correctness

DependenciesGraph.merge() can treat two direct deps as the same when either specifier is '*' and
then only updates the id, leaving the wildcard specifier in place. convertGraphToLockfile() later
matches root direct deps by exact specifier, so merged graphs can fail to emit importer entries for
non-wildcard manifest specs, producing an incomplete lockfile.
Agent Prompt
## Issue description
`DependenciesGraph.merge()` merges two root-edge neighbours if `isSameDirectDependency()` returns true. The helper currently considers a wildcard specifier (`*`/empty/undefined) compatible with any specifier, but the merge only updates `existingDirectDep.id` and does not update `existingDirectDep.specifier`.

Downstream, `convertGraphToLockfile()` searches for a root-edge neighbour by `(directDep.name === name && (directDep.specifier === specifier || dp.removeSuffix(directDep.id) === `${name}@${specifier}`))`. If the merged neighbour retained `specifier='*'` while the manifest specifier is `^x.y.z`, the lookup can fail and the importer dependency entry will not be written.

## Issue Context
A minimal failure scenario:
- Existing root neighbour: `{ name: 'foo', specifier: '*', id: 'foo@1.0.0' }`
- Incoming root neighbour: `{ name: 'foo', specifier: '^2.0.0', id: 'foo@2.0.0' }`
After merge, neighbour can become `{ name: 'foo', specifier: '*', id: 'foo@2.0.0' }`.
Then `convertGraphToLockfile()` searching for manifest `foo: '^2.0.0'` won’t match `directDep.specifier === specifier`, and won’t match the `${name}@${specifier}` fallback either.

## Fix Focus Areas
- scopes/scope/objects/models/dependencies-graph.ts[78-102]
- scopes/scope/objects/models/dependencies-graph.ts[260-268]
- scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[339-364]

## Suggested fix
Option A (recommended): when merging two direct deps that match by wildcard, prefer the more specific specifier:
- If `existing.specifier` is wildcard and `incoming.specifier` is not, set `existing.specifier = incoming.specifier`.
- If both are non-wildcard but differ, keep both entries (don’t treat them as identical).

Option B: tighten `isSameDirectDependency()` so wildcard does not match a concrete specifier (only treat as same when both specifiers are equal or both wildcard).

Add a unit test that merges graphs with wildcard vs non-wildcard specifiers and asserts `convertGraphToLockfile()` writes the importer dependency entry.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit dda3274

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant