Skip to content

fix(install): clear error when a component depends on an unpublished update-dependent#10427

Open
davidfirst wants to merge 8 commits into
masterfrom
fix-install-unpublished-update-dependent
Open

fix(install): clear error when a component depends on an unpublished update-dependent#10427
davidfirst wants to merge 8 commits into
masterfrom
fix-install-unpublished-update-dependent

Conversation

@davidfirst

Copy link
Copy Markdown
Member

Problem

A visible workspace component can depend on a hidden lane updateDependents entry (created by "snap updates" / Ripple). Those entries aren't checked out, so a dependent must fetch them from the registry as a package — but a package only exists once Ripple built the entry successfully. When the build failed (or never completed), bit install died with a cryptic pnpm error:

No matching version found for @scope/comp@0.0.0-<hash> while fetching it from https://node-registry.bit.cloud/

Fix

After manifests are built and before pnpm runs, check each checked-out component's snap dependencies: if a dep is in lane.updateDependents, isn't checked out, and its local Version.buildStatus isn't succeed, throw an actionable error instead:

unable to install the following update-dependent component(s) of the current lane.
their build did not complete successfully ...

  ✖ scope/comp (0.0.0-ab) — required by: scope/consumer

to resolve, import the component(s) ... linked from source instead of fetched from the registry:

  bit import scope/comp

Checked-out components are skipped (they're linked from source, not fetched), so the suggested bit import remediation makes the next install succeed.

Adds e2e scenario 21 to update-dependents-cascade.e2e.ts.

…update-dependent

A visible workspace component can depend on a hidden lane update-dependent
whose Ripple build failed (or never completed), so no package was published.
bit install then failed with a cryptic pnpm 'No matching version found' for the
unpublished 0.0.0-<hash>.

Detect this up-front: for each checked-out component's snap dependency that is a
lane update-dependent, not checked out, and whose local Version.buildStatus is not
'succeed', throw an actionable error suggesting 'bit import <id>' (which links it
from source instead of fetching from the registry).
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 15, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (1)

Grey Divider


Action required

1. Hardcoded in message 📘 Rule violation ⚙ Maintainability ⭐ New
Description
The new UpdateDependentBuildFailed error output hardcodes the Unicode em dash  in user-facing
CLI text. This violates the requirement to avoid hardcoded Unicode symbols in CLI output and to rely
on the shared formatting toolkit for consistent markers.
Code

scopes/workspace/install/exceptions/update-dependent-build-failed.ts[R29-33]

+        return formatItem(`${id} (${shortVersion}) — required by: ${requiredBy}`, errorSymbol);
+      })
+      .join('\n');
+    const importCommand = `bit import ${unpublished.map(({ id }) => id).join(' ')}`;
+    return `unable to install the following component(s) — they are pinned to a snap that was never published to the registry, so there is no package to install:
Evidence
PR Compliance ID 1 disallows introducing hardcoded Unicode symbols in CLI output and requires using
the shared CLI formatting toolkit. The new message text includes — required by: and `component(s)
— they are pinned...`, both of which hardcode the Unicode em dash in user-visible output.

CLAUDE.md: Use the shared CLI output formatting toolkit (do not hardcode chalk styles or Unicode symbols)
scopes/workspace/install/exceptions/update-dependent-build-failed.ts[29-33]

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 CLI error message in `UpdateDependentBuildFailed.formatMessage()` hardcodes the Unicode em dash `—` in output strings.

## Issue Context
The compliance checklist requires using the shared CLI output formatting toolkit and avoiding hardcoded Unicode symbols for consistent styling and maintainability.

## Fix Focus Areas
- scopes/workspace/install/exceptions/update-dependent-build-failed.ts[29-33]

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


2. Misclassifies pnpm failures 🐞 Bug ≡ Correctness
Description
enrichUnpublishedSnapDepError rewrites install failures based only on substring-matching
(packageName + snap hash) and can therefore replace non-"no matching version" pnpm errors (e.g.
network/auth/404) with UpdateDependentBuildFailed, masking the true root cause. This repo already
treats non-ERR_PNPM_NO_MATCHING_VERSION codes (including FETCH_404) as potentially real outages/auth
failures and avoids swallowing them, so this enrichment should be gated similarly.
Code

scopes/workspace/install/install.main.runtime.ts[R542-556]

+  private enrichUnpublishedSnapDepError(err: Error, components: Component[]): Error {
+    // checked-out components are linked from source, so they're never fetched from the registry.
+    const workspaceIds = this.workspace.listIds();
+    const errMessage = err.message || '';
+
+    const unpublished = new Map<string, { id: ComponentID; dependents: Set<string> }>();
+    for (const component of components) {
+      for (const dep of this.dependencyResolver.getComponentDependencies(component)) {
+        const depId = dep.componentId;
+        if (!depId.version || !isSnap(depId.version)) continue;
+        if (workspaceIds.hasWithoutVersion(depId)) continue;
+        // only the dependency the package manager actually failed on: its package + snap version appear in the
+        // error (the manifest version is `0.0.0-<hash>`, so the raw hash is a substring).
+        if (!errMessage.includes(dep.packageName) || !errMessage.includes(depId.version)) continue;
+        const key = depId.toStringWithoutVersion();
Evidence
The enrichment currently relies only on string inclusion checks and is invoked for any
package-manager error. The pnpm lockfile graph converter code documents that only
ERR_PNPM_NO_MATCHING_VERSION should be treated as an unpublished-snap signal, while FETCH_404 and
other codes must not be swallowed because they can represent auth/network failures.

scopes/workspace/install/install.main.runtime.ts[428-454]
scopes/workspace/install/install.main.runtime.ts[530-570]
scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[358-375]
scopes/dependencies/pnpm/lockfile-deps-graph-converter.spec.ts[870-908]

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

## Issue description
`InstallMain.enrichUnpublishedSnapDepError()` rewrites package-manager failures into `UpdateDependentBuildFailed` using only `err.message.includes(dep.packageName)` + `err.message.includes(depId.version)`. This can incorrectly rewrite auth/network/registry failures (including pnpm 404/auth-ambiguous cases), hiding important remediation.
### Issue Context
- pnpm errors are wrapped by `pnpmErrorToBitError()` and the original pnpm error is attached on `cause`.
- Elsewhere in the repo, only `ERR_PNPM_NO_MATCHING_VERSION` is treated as the “unpublished snap” signal; other codes (even `FETCH_404`) are explicitly not treated as such because they may indicate auth failures/outages.
### Fix Focus Areas
- scopes/workspace/install/install.main.runtime.ts[448-570]
### Implementation notes
- Extract pnpm error code safely, e.g. `const code = (err as any).cause?.code ?? (err as any).code;`.
- Only attempt the unpublished-snap enrichment when `code === 'ERR_PNPM_NO_MATCHING_VERSION'` (and/or when the message includes an unmistakable marker like `No matching version found`).
- For all other codes, return the original error unchanged.

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


3. Skipped status blocks install ✓ Resolved 🐞 Bug ≡ Correctness
Description
throwOnUnpublishedUpdateDependentDeps treats any buildStatus other than Succeed as unpublished, so
BuildStatus.Skipped will still be reported and can throw UpdateDependentBuildFailed. Elsewhere,
Skipped is treated as an acceptable “built enough” state (e.g., onlyIfBuilt/versionShouldBeBuilt),
so this new guard can introduce false-positive install failures.
Code

scopes/workspace/install/install.main.runtime.ts[R595-599]

+      const buildStatus = await readBuildStatus(id);
+      // skip when now known to be published (succeed) or undeterminable (version object not local).
+      if (buildStatus === null || buildStatus === BuildStatus.Succeed) continue;
+      unpublished.push({ id: id.toStringWithoutVersion(), version: id.version as string, dependents: [...dependents] });
+    }
Evidence
The install guard only whitelists BuildStatus.Succeed, while both the scope importer and sources
repository logic explicitly whitelist BuildStatus.Skipped as an acceptable built state. This
inconsistency means the new guard can fail installs in cases that the rest of the system would treat
as valid/handled.

scopes/workspace/install/install.main.runtime.ts[553-602]
components/legacy/scope/component-ops/scope-components-importer.ts[809-837]
components/legacy/scope/repositories/sources.ts[103-132]

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

## Issue description
`throwOnUnpublishedUpdateDependentDeps()` only considers `BuildStatus.Succeed` as safe. This makes `BuildStatus.Skipped` behave like an unpublished build and can throw `UpdateDependentBuildFailed`, even though other parts of the system treat `Skipped` equivalently to `Succeed` for “built” gating.
### Issue Context
The import pipeline uses `Skipped` as an allowed state in multiple places (e.g., `onlyIfBuilt` and `versionShouldBeBuilt` checks). The install-time guard should align with that behavior to avoid blocking valid scenarios.
### Fix Focus Areas
- scopes/workspace/install/install.main.runtime.ts[544-601]
- components/legacy/scope/component-ops/scope-components-importer.ts[809-837]
- components/legacy/scope/repositories/sources.ts[103-132]
### Proposed fix
- Update the checks to treat `BuildStatus.Skipped` as safe alongside `BuildStatus.Succeed`:
- In candidate collection: skip when status is `Succeed` **or** `Skipped`.
- In the final `unpublished` decision: continue when status is `null` **or** `Succeed` **or** `Skipped`.
- Add/adjust a test if needed to cover the `Skipped` case (if there is an existing e2e/unit harness that can produce it).

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



Remediation recommended

4. Loses original error cause 🐞 Bug ◔ Observability
Description
When a match is found, InstallMain throws UpdateDependentBuildFailed without attaching the original
pnpm/installer error as a cause, losing details such as auth hints, registry URL, and pnpm stack
context. This reduces observability and makes it harder to diagnose when the heuristic misfires or
when additional context is needed.
Code

scopes/workspace/install/exceptions/update-dependent-build-failed.ts[R19-22]

+export class UpdateDependentBuildFailed extends BitError {
+  constructor(readonly unpublished: UnpublishedSnapDependency[]) {
+    super(UpdateDependentBuildFailed.formatMessage(unpublished));
+  }
Evidence
pnpm errors are wrapped into a BitError that retains the original pnpm error under cause, but the
new flow constructs a fresh BitError without carrying that cause forward.

scopes/dependencies/pnpm/pnpm-error-to-bit-error.ts[15-20]
scopes/workspace/install/install.main.runtime.ts[542-570]
scopes/workspace/install/exceptions/update-dependent-build-failed.ts[19-41]

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

## Issue description
When `UpdateDependentBuildFailed` is thrown, it does not preserve the original underlying error (pnpm BitError / pnpm cause). This discards useful debugging context.
### Issue Context
`pnpmErrorToBitError()` already sets `newErr.cause = err` for pnpm errors. The new enrichment replaces that error entirely.
### Fix Focus Areas
- scopes/workspace/install/install.main.runtime.ts[542-570]
- scopes/workspace/install/exceptions/update-dependent-build-failed.ts[19-41]
### Implementation notes
- When creating `UpdateDependentBuildFailed`, attach the original error as the cause, e.g.:
- `const enriched = new UpdateDependentBuildFailed(...); (enriched as any).cause = err; return enriched;`
- Alternatively, extend the exception to accept a `cause` parameter and set it there.
- (Optional) Consider appending a short “original error:” section to the formatted message only in verbose/debug mode, but do keep the cause for programmatic access.

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


Grey Divider

Previous review results

Review updated until commit b85689b

Results up to commit N/A


🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0)


Action required
1. Misclassifies pnpm failures 🐞 Bug ≡ Correctness
Description
enrichUnpublishedSnapDepError rewrites install failures based only on substring-matching
(packageName + snap hash) and can therefore replace non-"no matching version" pnpm errors (e.g.
network/auth/404) with UpdateDependentBuildFailed, masking the true root cause. This repo already
treats non-ERR_PNPM_NO_MATCHING_VERSION codes (including FETCH_404) as potentially real outages/auth
failures and avoids swallowing them, so this enrichment should be gated similarly.
Code

scopes/workspace/install/install.main.runtime.ts[R542-556]

+  private enrichUnpublishedSnapDepError(err: Error, components: Component[]): Error {
+    // checked-out components are linked from source, so they're never fetched from the registry.
+    const workspaceIds = this.workspace.listIds();
+    const errMessage = err.message || '';
+
+    const unpublished = new Map<string, { id: ComponentID; dependents: Set<string> }>();
+    for (const component of components) {
+      for (const dep of this.dependencyResolver.getComponentDependencies(component)) {
+        const depId = dep.componentId;
+        if (!depId.version || !isSnap(depId.version)) continue;
+        if (workspaceIds.hasWithoutVersion(depId)) continue;
+        // only the dependency the package manager actually failed on: its package + snap version appear in the
+        // error (the manifest version is `0.0.0-<hash>`, so the raw hash is a substring).
+        if (!errMessage.includes(dep.packageName) || !errMessage.includes(depId.version)) continue;
+        const key = depId.toStringWithoutVersion();
Evidence
The enrichment currently relies only on string inclusion checks and is invoked for any
package-manager error. The pnpm lockfile graph converter code documents that only
ERR_PNPM_NO_MATCHING_VERSION should be treated as an unpublished-snap signal, while FETCH_404 and
other codes must not be swallowed because they can represent auth/network failures.

scopes/workspace/install/install.main.runtime.ts[428-454]
scopes/workspace/install/install.main.runtime.ts[530-570]
scopes/dependencies/pnpm/lockfile-deps-graph-converter.ts[358-375]
scopes/dependencies/pnpm/lockfile-deps-graph-converter.spec.ts[870-908]

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

## Issue description
`InstallMain.enrichUnpublishedSnapDepError()` rewrites package-manager failures into `UpdateDependentBuildFailed` using only `err.message.includes(dep.packageName)` + `err.message.includes(depId.version)`. This can incorrectly rewrite auth/network/registry failures (including pnpm 404/auth-ambiguous cases), hiding important remediation.
### Issue Context
- pnpm errors are wrapped by `pnpmErrorToBitError()` and the original pnpm error is attached on `cause`.
- Elsewhere in the repo, only `ERR_PNPM_NO_MATCHING_VERSION` is treated as the “unpublished snap” signal; other codes (even `FETCH_404`) are explicitly not treated as such because they may indicate auth failures/outages.
### Fix Focus Areas
- scopes/workspace/install/install.main.runtime.ts[448-570]
### Implementation notes
- Extract pnpm error code safely, e.g. `const code = (err as any).cause?.code ?? (err as any).code;`.
- Only attempt the unpublished-snap enrichment when `code === 'ERR_PNPM_NO_MATCHING_VERSION'` (and/or when the message includes an unmistakable marker like `No matching version found`).
- For all other codes, return the original error unchanged.

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


2. Skipped status blocks install ✓ Resolved 🐞 Bug ≡ Correctness
Description
throwOnUnpublishedUpdateDependentDeps treats any buildStatus other than Succeed as unpublished, so
BuildStatus.Skipped will still be reported and can throw UpdateDependentBuildFailed. Elsewhere,
Skipped is treated as an acceptable “built enough” state (e.g., onlyIfBuilt/versionShouldBeBuilt),
so this new guard can introduce false-positive install failures.
Code

scopes/workspace/install/install.main.runtime.ts[R595-599]

+      const buildStatus = await readBuildStatus(id);
+      // skip when now known to be published (succeed) or undeterminable (version object not local).
+      if (buildStatus === null || buildStatus === BuildStatus.Succeed) continue;
+      unpublished.push({ id: id.toStringWithoutVersion(), version: id.version as string, dependents: [...dependents] });
+    }
Evidence
The install guard only whitelists BuildStatus.Succeed, while both the scope importer and sources
repository logic explicitly whitelist BuildStatus.Skipped as an acceptable built state. This
inconsistency means the new guard can fail installs in cases that the rest of the system would treat
as valid/handled.

scopes/workspace/install/install.main.runtime.ts[553-602]
components/legacy/scope/component-ops/scope-components-importer.ts[809-837]
components/legacy/scope/repositories/sources.ts[103-132]

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

## Issue description
`throwOnUnpublishedUpdateDependentDeps()` only considers `BuildStatus.Succeed` as safe. This makes `BuildStatus.Skipped` behave like an unpublished build and can throw `UpdateDependentBuildFailed`, even though other parts of the system treat `Skipped` equivalently to `Succeed` for “built” gating.
### Issue Context
The import pipeline uses `Skipped` as an allowed state in multiple places (e.g., `onlyIfBuilt` and `versionShouldBeBuilt` checks). The install-time guard should align with that behavior to avoid blocking valid scenarios.
### Fix Focus Areas
- scopes/workspace/install/install.main.runtime.ts[544-601]
- components/legacy/scope/component-ops/scope-components-importer.ts[809-837]
- components/legacy/scope/repositories/sources.ts[103-132]
### Proposed fix
- Update the checks to treat `BuildStatus.Skipped` as safe alongside `BuildStatus.Succeed`:
- In candidate collection: skip when status is `Succeed` **or** `Skipped`.
- In the final `unpublished` decision: continue when status is `null` **or** `Succeed` **or** `Skipped`.
- Add/adjust a test if needed to cover the `Skipped` case (if there is an existing e2e/unit harness that can produce it).

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



Remediation recommended
3. Loses original error cause 🐞 Bug ◔ Observability
Description
When a match is found, InstallMain throws UpdateDependentBuildFailed without attaching the original
pnpm/installer error as a cause, losing details such as auth hints, registry URL, and pnpm stack
context. This reduces observability and makes it harder to diagnose when the heuristic misfires or
when additional context is needed.
Code

scopes/workspace/install/exceptions/update-dependent-build-failed.ts[R19-22]

+export class UpdateDependentBuildFailed extends BitError {
+  constructor(readonly unpublished: UnpublishedSnapDependency[]) {
+    super(UpdateDependentBuildFailed.formatMessage(unpublished));
+  }
Evidence
pnpm errors are wrapped into a BitError that retains the original pnpm error under cause, but the
new flow constructs a fresh BitError without carrying that cause forward.

scopes/dependencies/pnpm/pnpm-error-to-bit-error.ts[15-20]
scopes/workspace/install/install.main.runtime.ts[542-570]
scopes/workspace/install/exceptions/update-dependent-build-failed.ts[19-41]

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

## Issue description
When `UpdateDependentBuildFailed` is thrown, it does not preserve the original underlying error (pnpm BitError / pnpm cause). This discards useful debugging context.
### Issue Context
`pnpmErrorToBitError()` already sets `newErr.cause = err` for pnpm errors. The new enrichment replaces that error entirely.
### Fix Focus Areas
- scopes/workspace/install/install.main.runtime.ts[542-570]
- scopes/workspace/install/exceptions/update-dependent-build-failed.ts[19-41]
### Implementation notes
- When creating `UpdateDependentBuildFailed`, attach the original error as the cause, e.g.:
- `const enriched = new UpdateDependentBuildFailed(...); (enriched as any).cause = err; return enriched;`
- Alternatively, extend the exception to accept a `cause` parameter and set it there.
- (Optional) Consider appending a short “original error:” section to the formatted message only in verbose/debug mode, but do keep the cause for programmatic access.

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


Qodo Logo

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

Copy link
Copy Markdown

PR Summary by Qodo

fix(install): fail early on unpublished lane update-dependent dependencies
🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

Walkthroughs

Description
• Detect hidden lane update-dependent snap deps that were never successfully built/published.
• Fail bit install early with actionable error and bit import remediation.
• Add e2e coverage for install failure on unpublished update-dependent cascade snaps.
Diagram
graph TD
  A["InstallMain._installModules"] --> B["Build manifests"] --> C["Check lane updateDependents"] --> D{"Unpublished update-dependent?"}
  D -->|"yes"| E["Throw UpdateDependentBuildFailed"] --> F["User runs: bit import <id>"]
  D -->|"no"| G["Run pnpm install"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Probe registry for package existence
  • ➕ Most accurate signal (checks the real source of truth).
  • ➕ Avoids relying on local Version presence/buildStatus semantics.
  • ➖ Adds network latency and possible rate/availability issues to bit install.
  • ➖ Registry checks may require auth/headers and vary by package manager/registry implementation.
  • ➖ More complex failure modes (timeouts, partial results).
2. Auto-import hidden update-dependents when needed
  • ➕ May allow install to proceed without manual remediation.
  • ➕ Keeps workspace consistent by linking from source automatically.
  • ➖ Surprising side effect for an install command (mutates workspace contents).
  • ➖ Could import large dependency sets unintentionally.
  • ➖ Doesn’t help if the component source isn’t accessible/authorized.

Recommendation: Current approach (local lane/updateDependents + local buildStatus gate with an explicit bit import remediation) is a good tradeoff: it avoids network calls, fails before pnpm with a clear root cause, and gives a deterministic fix. Consider registry probing only if local buildStatus frequently can’t be determined (missing local Version objects) and users still hit pnpm’s cryptic error.

Grey Divider

File Changes

Enhancement (1)
index.ts Export new install exception for failed update-dependents +2/-0

Export new install exception for failed update-dependents

• Exports 'UpdateDependentBuildFailed' and its 'FailedUpdateDependent' payload type from the install exceptions barrel.

scopes/workspace/install/exceptions/index.ts


Bug fix (2)
update-dependent-build-failed.ts Introduce actionable error for unpublished update-dependent dependencies +41/-0

Introduce actionable error for unpublished update-dependent dependencies

• Adds a dedicated 'BitError' that formats a clear failure message listing update-dependent components, their snap hashes, and which workspace components require them. Includes a suggested 'bit import ...' remediation to link from source.

scopes/workspace/install/exceptions/update-dependent-build-failed.ts


install.main.runtime.ts Pre-flight validation for unpublished lane update-dependent snap deps +74/-1

Pre-flight validation for unpublished lane update-dependent snap deps

• Adds a pre-pnpm validation step that scans checked-out components’ snap dependencies, intersects them with 'lane.updateDependents', skips checked-out deps, and checks local 'Version.buildStatus'. Throws 'UpdateDependentBuildFailed' when the build status is not 'succeed', preventing pnpm’s cryptic "No matching version found" failure.

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


Tests (1)
update-dependents-cascade.e2e.ts Add e2e for install failure on unpublished update-dependent snap +67/-0

Add e2e for install failure on unpublished update-dependent snap

• Introduces scenario 21 covering a lane where a visible component depends on a hidden updateDependent snap that was never built/published. Asserts 'bit install' fails with an actionable message that names the component and suggests 'bit import'.

e2e/harmony/lanes/update-dependents-cascade.e2e.ts


Grey Divider

Qodo Logo

Comment thread scopes/workspace/install/exceptions/update-dependent-build-failed.ts Outdated
Comment thread scopes/workspace/install/install.main.runtime.ts Outdated
…hared CLI formatter

Address review feedback:
- A locally-stale pending/failed buildStatus could wrongly fail install when the
  remote already built/published the snap. Refresh the suspect versions from the
  remote (reFetchUnBuiltVersion) before deciding they're unpublished.
- Use errorSymbol/formatItem from @teambit/cli instead of hardcoding the Unicode
  symbol, per the CLI output style guide.
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 16, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 16b5328

Comment thread scopes/workspace/install/install.main.runtime.ts Outdated
…t guard

BuildStatus.Skipped is treated as 'built enough' elsewhere (onlyIfBuilt in
scope-components-importer, sources.get), so the install guard now treats both
Succeed and Skipped as built and won't report a Skipped update-dependent as
unpublished.
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 16, 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

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

qodo-free-for-open-source-projects Bot commented Jun 16, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 7a54a97

…-manager failure

Replace the pre-emptive per-install scan (which ran a dependency walk + remote
refetch on every install) with a catch around the package-manager step: only when
it fails with 'No matching version found' do we check whether the failing package
is a component-dependency snap that isn't checked out, and if so surface the
actionable 'bit import' message.

Match against the package-manager error rather than lane.updateDependents, since
the install's import phase may have already dropped the entry from the local lane
object. This also removes the buildStatus/Skipped/refetch logic (the failure itself
proves the package is unpublished), so there is zero overhead on successful installs.

The e2e now uses the npm CI registry so the unpublished snap genuinely 404s.
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 16, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 8b4c264

…ager error

bit import does not remove entries from lane.updateDependents (only snap/merge/reset
and the explicit removeUpdateDependents command do, via addVersion). Fix the comment
that wrongly attributed a missing entry to the import phase; the real reason to match
the package-manager error is that it pinpoints the failing version and a consumer can
keep pinning a never-published snap after the entry was promoted out of updateDependents.
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 16, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 704078f

… error

The matcher keys off the package-manager 'No matching version found' error, not
lane.updateDependents membership, so it can legitimately fire for any snap dependency
that isn't checked out and was never published (e.g. after a reset that rewound a
component to a never-published snap). Reword the message to name that as the likely
cause rather than asserting the component is an update-dependent. Rename the payload
type FailedUpdateDependent -> UnpublishedSnapDependency accordingly.
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 16, 2026

Copy link
Copy Markdown

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

Comment on lines +542 to +556
private enrichUnpublishedSnapDepError(err: Error, components: Component[]): Error {
// checked-out components are linked from source, so they're never fetched from the registry.
const workspaceIds = this.workspace.listIds();
const errMessage = err.message || '';

const unpublished = new Map<string, { id: ComponentID; dependents: Set<string> }>();
for (const component of components) {
for (const dep of this.dependencyResolver.getComponentDependencies(component)) {
const depId = dep.componentId;
if (!depId.version || !isSnap(depId.version)) continue;
if (workspaceIds.hasWithoutVersion(depId)) continue;
// only the dependency the package manager actually failed on: its package + snap version appear in the
// error (the manifest version is `0.0.0-<hash>`, so the raw hash is a substring).
if (!errMessage.includes(dep.packageName) || !errMessage.includes(depId.version)) continue;
const key = depId.toStringWithoutVersion();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Misclassifies pnpm failures 🐞 Bug ≡ Correctness

enrichUnpublishedSnapDepError rewrites install failures based only on substring-matching
(packageName + snap hash) and can therefore replace non-"no matching version" pnpm errors (e.g.
network/auth/404) with UpdateDependentBuildFailed, masking the true root cause. This repo already
treats non-ERR_PNPM_NO_MATCHING_VERSION codes (including FETCH_404) as potentially real outages/auth
failures and avoids swallowing them, so this enrichment should be gated similarly.
Agent Prompt
### Issue description
`InstallMain.enrichUnpublishedSnapDepError()` rewrites package-manager failures into `UpdateDependentBuildFailed` using only `err.message.includes(dep.packageName)` + `err.message.includes(depId.version)`. This can incorrectly rewrite auth/network/registry failures (including pnpm 404/auth-ambiguous cases), hiding important remediation.

### Issue Context
- pnpm errors are wrapped by `pnpmErrorToBitError()` and the original pnpm error is attached on `cause`.
- Elsewhere in the repo, only `ERR_PNPM_NO_MATCHING_VERSION` is treated as the “unpublished snap” signal; other codes (even `FETCH_404`) are explicitly not treated as such because they may indicate auth failures/outages.

### Fix Focus Areas
- scopes/workspace/install/install.main.runtime.ts[448-570]

### Implementation notes
- Extract pnpm error code safely, e.g. `const code = (err as any).cause?.code ?? (err as any).code;`.
- Only attempt the unpublished-snap enrichment when `code === 'ERR_PNPM_NO_MATCHING_VERSION'` (and/or when the message includes an unmistakable marker like `No matching version found`).
- For all other codes, return the original error unchanged.

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

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

qodo-free-for-open-source-projects Bot commented Jun 16, 2026

Copy link
Copy Markdown

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

Comment on lines +29 to +33
return formatItem(`${id} (${shortVersion}) — required by: ${requiredBy}`, errorSymbol);
})
.join('\n');
const importCommand = `bit import ${unpublished.map(({ id }) => id).join(' ')}`;
return `unable to install the following component(s) — they are pinned to a snap that was never published to the registry, so there is no package to install:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Hardcoded in message 📘 Rule violation ⚙ Maintainability

The new UpdateDependentBuildFailed error output hardcodes the Unicode em dash  in user-facing
CLI text. This violates the requirement to avoid hardcoded Unicode symbols in CLI output and to rely
on the shared formatting toolkit for consistent markers.
Agent Prompt
## Issue description
The CLI error message in `UpdateDependentBuildFailed.formatMessage()` hardcodes the Unicode em dash `—` in output strings.

## Issue Context
The compliance checklist requires using the shared CLI output formatting toolkit and avoiding hardcoded Unicode symbols for consistent styling and maintainability.

## Fix Focus Areas
- scopes/workspace/install/exceptions/update-dependent-build-failed.ts[29-33]

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

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