Skip to content

feat(ci): sync consumes dependency-context drift; lane snaps scope to git-authored changes - #10574

Open
luvkapur wants to merge 14 commits into
masterfrom
fix/ci-sync-scope-status-verification
Open

feat(ci): sync consumes dependency-context drift; lane snaps scope to git-authored changes#10574
luvkapur wants to merge 14 commits into
masterfrom
fix/ci-sync-scope-status-verification

Conversation

@luvkapur

@luvkapur luvkapur commented Aug 6, 2026

Copy link
Copy Markdown
Member

Problem

A converged mirror workspace shows 26 of 42 components as modified. Git introduces no change. The recorded Version carries bitVersion: 1.12.61. The mirror runs engine 2.0.69. The diff is confined to dependency data: @types/react moves from ^17.0.8 to ^19.0.0, for example.

Cause

The engine pin in workspace.jsonc is git state. The core env's dependency templates are a function of that pin. A pin bump moves the template — a real, git-introduced dependency change, not noise. Nothing in bit ci sync consumed it: a lane run swept it into the dev's snap, and a main run had no step for it.

Fix

Three mechanisms:

  1. Detector (context-drift-detector.ts): classifies a tag-pending component as drift when its diff against the recorded Version is confined to dependency fields, including the env-computed overrides key.
  2. Main convergence (convergeContextDrift): tags exactly the drifted set with a patch bump and exports, riding the existing bit-sync/main commit flow.
  3. Lane pending-minus-drift snap (lane-sync-executor.ts): a lane run snaps only the git-authored subset and reports the drift; a drifted component that a snapped component depends on may still auto-snap as that dependent.

This removes this branch's earlier pending-set verification scoping in favor of the narrower, explicit snapIds scoping above. bit ci pr and bit ci merge do not change: neither passes snapIds, so their verification stays global.

Tests

Suite Result
tsc --noEmit clean
bit test teambit.git/ci 230 passing
ci-sync.e2e.ts 41 passing
ci-sync-state.e2e.ts 11 passing
ci-commands.e2e.ts 87 passing
ci-bitmap-auto-sync.e2e.ts 58 passing
oxlint --deny-warnings scopes/git/ci e2e/harmony/ci-sync.e2e.ts 0 warnings, 0 errors
prettier --check clean

…s or tags

A real scope carries components with tag blockers (circular
dependencies on teambit.api-reference). The global verification
halted every snap in the repository, while bit snap itself scopes
its checks to the snapped components. The verification now fails
only on issues in listTagPendingIds; bit ci verify stays global.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

fix(ci): scope workspace-status failures to snapped/tagged components

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Scope CI status verification failures to components included in snap/tag runs.
• Keep full status output, but ignore blockers outside pending set and warn.
• Add e2e regression for circular-dependency blocker on untouched components.
Diagram

graph TD
  A["bit ci sync/pr/merge"] --> B["CiMain.verifyWorkspaceStatusInternal"] --> C["StatusMain.status + formatStatusOutput"] --> D["Workspace status output"]
  B --> E["Workspace.listTagPendingIds"] --> F["Filter componentsWithIssues"] --> G["Recompute exit code"]
  G --> H["Proceed or throw"]
  I["e2e: ci-sync.e2e.ts"] --> A

  subgraph Legend
    direction LR
    _cmd["Command"] ~~~ _mod["Module/Method"] ~~~ _data["Data/Output"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Push scoping into Status API
  • ➕ Single place to define “scoped failure” semantics
  • ➕ Avoids recomputing code via a second formatStatusOutput call
  • ➖ Requires changing @teambit/status public API (wider blast radius)
  • ➖ Harder to keep “print global status but fail scoped” behavior explicit at call sites
2. Fail only after snap/tag planning step
  • ➕ Scopes based on the exact snap/tag plan, not just listTagPendingIds
  • ➕ Could align better with future “git diff-based” snap set improvements
  • ➖ Bigger refactor of CI flow ordering
  • ➖ More moving parts and additional failure modes before verification runs

Recommendation: Current approach is a good tactical fix: it preserves full status visibility while aligning failure semantics with the snap/tag scope for sync/pr/merge. If this behavior becomes broadly desirable beyond CI (or more commands adopt it), consider promoting the scoping logic into the status layer to avoid duplicated recomputation and to formalize the contract.

Files changed (2) +69 / -11

Bug fix (1) +35 / -11
ci.main.runtime.tsScope CI workspace-status verification failure to pending snap/tag components +35/-11

Scope CI workspace-status verification failure to pending snap/tag components

• Extends 'verifyWorkspaceStatusInternal' with an option to recompute the failure code using only 'componentsWithIssues' that are in 'workspace.listTagPendingIds', while still printing the full status output. Enables the scoped behavior for snap/PR and merge flows, and logs a warning summary when only out-of-scope issues exist; 'bit ci verify' remains global.

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

Tests (1) +34 / -0
ci-sync.e2e.tsAdd e2e covering out-of-scope tag blockers during lane sync snaps +34/-0

Add e2e covering out-of-scope tag blockers during lane sync snaps

• Introduces a regression test where two main-branch components have a circular dependency (tag blocker) while the lane snap only touches a different component. Asserts 'bit ci sync' succeeds, does not emit the verification failure, and exports the expected lane update.

e2e/harmony/ci-sync.e2e.ts

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

qodo-free-for-open-source-projects Bot commented Aug 6, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Action required

1. Empty sync commits pushed ✓ Resolved 🐞 Bug ☼ Reliability
Description
When snapIds resolves to an empty set, CiMain.snapPrCommit() returns early without
snapping/exporting, but lane sync continues and pushes a --allow-empty sync commit anyway. This
can create misleading history and can retrigger CI repeatedly even though the lane/branch state did
not change.
Code

scopes/git/ci/ci.main.runtime.ts[R779-780]

+      this.logger.console(formatWarningSummary('No git-authored changes to snap'));
+      return 'No changes detected, nothing to snap';
Evidence
snapPrCommit() now exits successfully on an empty resolved snapIds set; lane sync ignores this
no-op and still records lane head, and commitAllAndPush() is explicitly configured to create/push
an empty commit (--allow-empty).

scopes/git/ci/ci.main.runtime.ts[775-781]
scopes/git/ci/sync/lane-sync-executor.ts[777-805]
scopes/git/ci/sync/lane-sync-executor.ts[609-636]
scopes/git/ci/sync/lane-sync-executor.ts[815-824]
scopes/git/ci/sync/lane-sync-executor.ts[1233-1245]

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.snapPrCommit()` can now return early for an empty `snapIds` set. In lane sync flows, that no-op is treated as success and the executor proceeds to `recordLaneHeadOnBranch()`, whose `commitAllAndPush()` always uses `--allow-empty`, so the run can push an empty sync commit.
### Issue Context
This becomes much more likely now that dependency-context drift is intentionally excluded from lane snaps: a lane run may have drift but zero git-authored components to snap.
### Fix Focus Areas
- Detect the no-op case and **skip** `recordLaneHeadOnBranch()` (or skip commit/push) when nothing was snapped/exported.
- Alternatively (or additionally), make `commitAllAndPush()` refuse to create/push empty commits by default (e.g., check `git status --porcelain` after staging, or catch the “nothing to commit” error and treat it as success without pushing).
#### Code references
- scopes/git/ci/ci.main.runtime.ts[775-781]
- scopes/git/ci/sync/lane-sync-executor.ts[777-805]
- scopes/git/ci/sync/lane-sync-executor.ts[609-636]
- scopes/git/ci/sync/lane-sync-executor.ts[1233-1245]

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


2. Undefined auto-snap results 🐞 Bug ≡ Correctness
Description
CiMain.snapAndExportReusingLane() calls .filter() on autoSnappedResults without a nullish
fallback, which can throw a TypeError and abort lane sync after snapping but before export. This
happens when snapping.snap() returns autoSnappedResults as undefined (it is assigned directly
from makeVersion()’s autoTaggedResults).
Code

scopes/git/ci/ci.main.runtime.ts[R1066-1069]

+          ...new Set(
+            autoSnappedResults
+              .filter((r) => driftSet.has(r.component.id.toStringWithoutVersion()))
+              .map((r) => r.component.id.toStringWithoutVersion())
Evidence
The PR adds logic that calls .filter() on autoSnappedResults without guarding for undefined.
The snap implementation populates autoSnappedResults directly from autoTaggedResults, and
elsewhere in the same file autoTaggedResults is treated as optional via || [], so the undefined
case is realistic and would crash at runtime.

scopes/git/ci/ci.main.runtime.ts[1058-1077]
scopes/component/snapping/snapping.main.runtime.ts[642-651]
scopes/component/snapping/snapping.main.runtime.ts[548-553]

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.snapAndExportReusingLane()` assumes `autoSnappedResults` is always an array and calls `.filter()` on it. If the snap result contains `autoSnappedResults: undefined`, this crashes with `TypeError: Cannot read properties of undefined (reading 'filter')`, aborting the sync run post-snap and pre-export.
### Issue Context
`snapping.snap()` assigns `autoSnappedResults` directly from `makeVersion()`’s `autoTaggedResults` (no defaulting), and other codepaths treat `autoTaggedResults` as optional.
### Fix Focus Areas
- scopes/git/ci/ci.main.runtime.ts[1058-1070]
- scopes/component/snapping/snapping.main.runtime.ts[642-651]
### Suggested fix
In `snapAndExportReusingLane`, ensure you always operate on an array:
- Use a default: `const { snappedComponents, autoSnappedResults = [] } = results as any;`
or `const autoSnappedResults = results.autoSnappedResults ?? [];`
- Then run the `.filter()`/`.map()` logic on that array.
Optionally, also harden `snapping.snap()` to always return `autoSnappedResults: autoTaggedResults ?? []` for runtime safety.

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


3. Drift diff misses normalization 🐞 Bug ≡ Correctness
Description
detectContextDrift() diffs recorded.id() vs fromFs.id() without applying the legacy
normalizations that ignore deprecated file name/test metadata and file ordering, so it can
spuriously treat a dependency-only drift as a file change and classify it as git-authored. This
breaks the PR’s intended behavior by incorrectly including drifted components in the lane snap set.
Code

scopes/git/ci/sync/context-drift-detector.ts[R40-43]

+      const comp = await workspace.get(id);
+      const consumerComp = comp.state._consumer.clone();
+      consumerComp.log = recorded.log; // same normalization as consumer.isComponentModified
+      const { version: fromFs } = await legacyScope.sources.consumerComponentToVersion(consumerComp);
Evidence
The drift detector rebuilds a Version via consumerComponentToVersion() and immediately diffs
.id() JSON, but consumerComponentToVersion() preserves file ordering and emits name/test from
filesystem data. The legacy modification semantics explicitly sort files and align name/test to
avoid treating those differences as modifications, so the detector can incorrectly see a files
diff and misclassify drift as git-authored.

scopes/git/ci/sync/context-drift-detector.ts[38-47]
components/legacy/scope/repositories/sources.ts[281-305]
components/legacy/consumer/consumer.ts[320-370]

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

## Issue description
`detectContextDrift()` rebuilds a filesystem `Version` using `consumerComponentToVersion()` and then diffs `JSON.parse(recorded.id())` against `JSON.parse(fromFs.id())`. Unlike the legacy modified-check, it does not normalize file ordering and deprecated file fields (`name`, `test`), so harmless differences can show up as a `files` change and force `depOnly=false`.
### Issue Context
- `consumerComponentToVersion()` constructs `files` from `file.basename` and `file.test` and preserves the current order.
- Legacy `Consumer.isComponentModified` explicitly (a) sorts files by `relativePath` and (b) aligns `name`/`test` between model and filesystem before comparing `Version.id()/calculateHash()`.
### Fix Focus Areas
- scopes/git/ci/sync/context-drift-detector.ts[38-47]
- components/legacy/scope/repositories/sources.ts[281-305]
- components/legacy/consumer/consumer.ts[320-370]
### Suggested fix
Before calling `recorded.id()` / `fromFs.id()` and classifying the diff:
1. Sort both `recorded.files` and `fromFs.files` by `relativePath`.
2. For each `fromFs.files[i]`, if a recorded file with the same `relativePath` exists, copy `name` and `test` from recorded onto `fromFs` (mirroring `Consumer.isComponentModified`).
3. Also normalize other hash-sensitive fields the legacy code normalizes (deps order, packageDeps key order, overrides key order, extensions sortById) to prevent order-only noise.
Add a focused unit test (or e2e) demonstrating that a dependency-only drift with a differing file order / file.name does not flip `depOnly` to false.

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


View more (3)
4. Auto-tags outside drift set 🐞 Bug ≡ Correctness
Description
CiMain.convergeContextDrift() calls snapping.tag() with autoTagReleaseType='patch' without disabling
auto-tagging, so dependents may be auto-tagged beyond the detected drift IDs. This breaks the
method’s stated intent (“exactly the drifted set”) and can create extra version bumps unrelated to
context drift.
Code

scopes/git/ci/ci.main.runtime.ts[R648-651]

+      message,
+      releaseType: 'patch',
+      autoTagReleaseType: 'patch',
+      ignoreIssues,
Evidence
The convergence code sets autoTagReleaseType: 'patch' and does not set skipAutoTag, while
snapping.tag() explicitly supports both parameters and forwards them to version-making, producing
autoTaggedResults (i.e., extra components can be tagged beyond the explicit ids).

scopes/git/ci/ci.main.runtime.ts[621-669]
scopes/component/snapping/snapping.main.runtime.ts[194-235]
scopes/component/snapping/snapping.main.runtime.ts[268-313]

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.convergeContextDrift()` intends to tag *only* the detected drift components, but the current `snapping.tag()` invocation can auto-tag dependents because `skipAutoTag` is not set (defaults to false) while `autoTagReleaseType` is provided.
## Issue Context
This can lead to patch bumps (and later export) of components that are not in the drift set, which contradicts the method contract and can widen the blast radius of a main-sync run.
## Fix Focus Areas
- scopes/git/ci/ci.main.runtime.ts[646-655]

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


5. Unscoped drift export ✓ Resolved 🐞 Bug ≡ Correctness
Description
CiMain.convergeContextDrift() calls exporter.export() without ids, which exports all staged
components in the workspace, not just the drifted components it just tagged. This can
unintentionally push unrelated staged snaps/tags during a main-sync run.
Code

scopes/git/ci/ci.main.runtime.ts[R665-667]

+    this.logger.console(chalk.blue(message));
+    await this.exporter.export();
+    const count = results.taggedComponents.length;
Evidence
The convergence flow invokes export() with no params, and ExportMain.export() takes optional
ids; when omitted, the exporter computes what to export from the workspace staged set. This makes
drift convergence export scope broader than the drift set.

scopes/git/ci/ci.main.runtime.ts[621-669]
scopes/scope/export/export.main.runtime.ts[85-90]
scopes/scope/export/export.main.runtime.ts[118-121]

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.convergeContextDrift()` currently runs `this.exporter.export()` without specifying `ids`, which makes export operate on the workspace’s full staged set. If any other components are staged (for any reason), they will be exported as part of drift convergence.
## Issue Context
`ExportMain.export()` supports `params.ids?: string[]`. When `ids` is omitted, `exportComponents()` computes `idsToExport` from the workspace staged state.
## Fix Focus Areas
- scopes/git/ci/ci.main.runtime.ts[665-667]
- scopes/scope/export/export.main.runtime.ts[85-90]

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


6. Scoped set incomplete ✓ Resolved 🐞 Bug ≡ Correctness
Description
verifyWorkspaceStatusInternal({ scopeToPendingComponents: true }) filters blocking issues only to
workspace.listTagPendingIds(), but snap/tag can also include soft-tagged components (persist mode)
and auto-tag dependents that are not in that list. This can log that issues “do not block” the run
and then still fail later during snap/tag on those excluded components.
Code

scopes/git/ci/ci.main.runtime.ts[R461-465]

+      const pending = ComponentIdList.fromArray(await this.workspace.listTagPendingIds());
+      const scoped = {
+        ...status,
+        componentsWithIssues: status.componentsWithIssues.filter((c) => pending.hasWithoutVersion(c.id)),
+      };
Evidence
The CI scoping uses only listTagPendingIds() to decide which issues can block, but
listTagPendingIds() is not the full set of components a tag/snap may operate on (persist-mode uses
soft-tagged components, and auto-tag dependents are computed separately). This mismatch can make CI
proceed past verification even though a later snap/tag step will still process a component with a
tag blocker.

scopes/git/ci/ci.main.runtime.ts[443-474]
scopes/workspace/workspace/workspace.ts[572-580]
scopes/component/snapping/snapping.main.runtime.ts[1399-1424]
scopes/workspace/workspace/workspace.ts[424-435]
scopes/component/status/status-formatter.ts[182-187]

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

## Issue description
`verifyWorkspaceStatusInternal()` recomputes `effectiveCode` by filtering `status.componentsWithIssues` to only components in `workspace.listTagPendingIds()`. However, the subsequent snap/tag operation may process additional components that are not in `listTagPendingIds()` (notably soft-tagged components when tagging with `persist`, and auto-tag dependents). This can cause CI to incorrectly treat real tag blockers as non-blocking and proceed.
## Issue Context
- `listTagPendingIds()` is defined as new+modified+removed+during-merge only.
- Tagging with `persist` explicitly selects `workspace.filter.bySoftTagged()` instead of `listTagPendingIds()`.
- Auto-tag dependents are a separate set (`workspace.listAutoTagPendingComponentIds()` / status “components pending auto-tag”) and can be tagged/snapped as part of the operation.
## Fix Focus Areas
- scopes/git/ci/ci.main.runtime.ts[443-474]
- scopes/workspace/workspace/workspace.ts[572-580]
- scopes/component/snapping/snapping.main.runtime.ts[1399-1424]
- scopes/workspace/workspace/workspace.ts[424-435]
## Suggested fix
1. When `scopeToPendingComponents` is enabled, build the “pending” set as a union of all component IDs that the upcoming operation can process, e.g.:
- `await workspace.listTagPendingIds()`
- `await workspace.listAutoTagPendingComponentIds()` (auto-tag dependents)
- `workspace.filter.bySoftTagged()` (for persist-mode runs)
Optionally include any other known snap/tag inputs relevant to your CI flows.
2. Filter `status.componentsWithIssues` against that union when recomputing `effectiveCode`.
3. Consider adjusting the warning text to avoid claiming “does not snap or tag” unless the scoped set is truly the exact snap/tag set for the run.
4. Add/extend an e2e to cover a blocker on an auto-tag dependent and/or a persist soft-tagged component to prevent regression.

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



Remediation recommended

7. main-config-sync uses raw chalk 📘 Rule violation ⚙ Maintainability ⭐ New
Description
Multiple new/updated CI sync and context-drift code paths emit CLI messages using direct
chalk.*(...) styling (blue/yellow/green) via logger.console(...) instead of the shared
@teambit/cli output formatting toolkit. This violates the repository CLI output style guide and
can lead to inconsistent command output styling.
Code

scopes/git/ci/sync/main-config-sync.ts[88]

+  logger.console(chalk.blue(`Syncing config changes from ${mainLaneId.toString()} into ${laneId.toString()}`));
Evidence
The repository CLI output style guide requires using the shared formatting toolkit from
@teambit/cli and discourages ad-hoc/raw chalk usage for command output. The new
sync/main-config-sync.ts introduces several logger.console(chalk.*(...)) lines (including a blue
“Syncing config...” message), and the new/updated drift-related flows similarly print a warning with
chalk.yellow(...) in detectContextDrift(), plus blue lines for the convergence message in
convergeContextDrift() and the auto-snapped drift report in snapAndExportReusingLane(), all of
which bypass the shared formatter and thus demonstrate the inconsistency.

CLAUDE.md: CLI Output Must Follow Style Guide and Use Shared Output Formatter (No Hardcoded Chalk Styles)
scopes/harmony/cli/cli-output-style-guide.md[1-4]
scopes/git/ci/sync/main-config-sync.ts[88-89]
scopes/git/ci/sync/context-drift-detector.ts[63-66]
scopes/git/ci/ci.main.runtime.ts[1101-1104]
scopes/git/ci/ci.main.runtime.ts[681-686]

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

## Issue description
Several new CLI outputs in the main-config sync and context drift flows use direct `chalk.blue/yellow/green(...)` styling (e.g., `logger.console(chalk.*(...))`) instead of the shared CLI output formatting toolkit required by the repository style guide.

## Issue Context
The repository’s CLI output style guide mandates using the shared formatter toolkit from `@teambit/cli` for consistent styling and explicitly discourages ad-hoc/raw `chalk` usage (and hardcoded styling/symbols) in command output. The current implementation prints multiple styled lines directly in `main-config-sync.ts`, emits a drift warning via `chalk.yellow(...)` in `detectContextDrift()`, and prints blue lines for both the convergence message and the auto-snapped drift report in the CI runtime, all of which should be migrated to the shared formatter.

## Fix Focus Areas
- scopes/git/ci/sync/main-config-sync.ts[34-41]
- scopes/git/ci/sync/main-config-sync.ts[88-109]
- scopes/git/ci/sync/main-config-sync.ts[236-245]
- scopes/git/ci/sync/context-drift-detector.ts[63-67]
- scopes/git/ci/ci.main.runtime.ts[681-689]
- scopes/git/ci/ci.main.runtime.ts[1099-1105]

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


8. Auto-tags bypass ignored blockers 🐞 Bug ≡ Correctness
Description
CiMain.convergeContextDrift() derives ignoreIssues from drifted components and passes it to
snapping.tag(), but Bit applies this ignore list to auto-tagged dependents too. As a result, an
auto-tagged (non-drift) dependent with a tag-blocker issue whose type is in that ignore list can be
tagged/exported during convergence when it should have blocked.
Code

scopes/git/ci/ci.main.runtime.ts[R666-669]

+      releaseType: 'patch',
+      autoTagReleaseType: 'patch',
+      ignoreIssues,
+      build: undefined,
Evidence
convergeContextDrift() computes an ignore list from drifted components and passes it into
snapping.tag(). The tag flow threads ignoreIssues into VersionMaker, which validates
auto-tagged components using builder.throwForComponentIssues(..., ignoreIssues). The builder
removes ignored issues before checking shouldBlockTagging(), so any auto-tagged component with a
blocker whose type appears in the drift-derived ignore list can slip through blocker validation.

scopes/git/ci/ci.main.runtime.ts[657-669]
scopes/component/snapping/snapping.main.runtime.ts[193-303]
scopes/component/snapping/version-maker.ts[113-127]
scopes/pipelines/builder/builder.main.runtime.ts[518-533]

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

## Issue description
`convergeContextDrift()` passes `ignoreIssues` into `snapping.tag()`. In Bit, this flag is used when validating both the explicit tag targets and any auto-tagged dependents, so blocker types tolerated for drifted components can accidentally be tolerated for auto-tagged, non-drift components as well.
### Issue Context
- `ignoreIssues` is a *global* comma-separated list; it is not scoped per component.
- Auto-tagging is enabled for convergence (`autoTagReleaseType: 'patch'`), so dependents may be included in the same tag operation.
### Fix Focus Areas
- Add a post-tag validation step in `convergeContextDrift()` that re-checks **auto-tagged components not in the drift set** for tag-blocker issues **without** the drift-derived ignore list, and abort before export if any would block.
- Optionally tighten `blockerNamesUnion()`/ignore list construction to include only true blocker issue types (not all issue names on a component).
- scopes/git/ci/ci.main.runtime.ts[657-691]
- scopes/component/snapping/version-maker.ts[113-127]
- scopes/pipelines/builder/builder.main.runtime.ts[518-533]
- scopes/component/snapping/snapping.main.runtime.ts[193-303]

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


9. Stale status scoping ✓ Resolved 🐞 Bug ◔ Observability
Description
snapPrCommit() scopes workspace-status verification using the initial snapIds, but
snapAndExportReusingLane() can later expand snapIds after syncConfigFromMain(). This makes the
earlier “issues … do not block” output potentially incorrect and can defer tag-blocker failures to
the snap phase unexpectedly.
Code

scopes/git/ci/ci.main.runtime.ts[R943-946]

+            if (snapIds) {
+              const { gitAuthored } = await this.detectContextDrift();
+              const known = new Set(snapIds.map((id) => id.toStringWithoutVersion()));
+              const missing = gitAuthored.filter((id) => !known.has(id.toStringWithoutVersion()));
Evidence
Status scoping and its “do not block” warning are computed using the initial resolvedSnapIds, but
the code later mutates snapIds after config sync and uses the mutated list to drive the actual
snap (legacyBitIds).

scopes/git/ci/ci.main.runtime.ts[465-477]
scopes/git/ci/ci.main.runtime.ts[775-784]
scopes/git/ci/ci.main.runtime.ts[939-948]
scopes/git/ci/ci.main.runtime.ts[1064-1071]

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

## Issue description
Workspace status verification is performed before lane config sync, using the initial `snapIds`. Later, `snapAndExportReusingLane()` may expand `snapIds` and pass the expanded set into `snapping.snap()`, meaning:
- the earlier status scoping/warning can be misleading (it may claim issues don’t block because they’re “not snapped”, but they later become snapped), and
- failures can shift from the status-check step to the snap step for components added later.
### Issue Context
The PR intentionally expands `snapIds` after `syncConfigFromMain()` because the config sync can make additional components become “git-authored”. That expansion needs to be reflected in any status scoping / messaging.
### Fix Focus Areas
- Re-run (or defer) `verifyWorkspaceStatusInternal()` until after `syncConfigFromMain()` and any `snapIds` expansion is complete, using the final `snapIds` that will be passed to `snapping.snap()`.
- If re-running is too expensive, at least avoid printing the “issues above … do not block” message when `snapIds` is not yet final.
#### Code references
- scopes/git/ci/ci.main.runtime.ts[465-477]
- scopes/git/ci/ci.main.runtime.ts[775-784]
- scopes/git/ci/ci.main.runtime.ts[939-948]
- scopes/git/ci/ci.main.runtime.ts[1064-1071]

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


View more (1)
10. Serial drift detection work ✓ Resolved 🐞 Bug ➹ Performance
Description
detectContextDrift() processes pending components sequentially, performing multiple awaited loads
per component (model/version load, workspace load, rebuild Version payload). Since it’s called for
each lane sync run (and may be called again after config sync), total runtime scales linearly with
the number of pending components and can become a CI bottleneck on large pending sets.
Code

scopes/git/ci/sync/context-drift-detector.ts[R38-41]

+      const modelComponent = await legacyScope.getModelComponent(id);
+      const recorded = await modelComponent.loadVersion(id.version as string, repo);
+      const comp = await workspace.get(id);
+      const consumerComp = comp.state._consumer.clone();
Evidence
The detector performs sequential per-component awaits inside a for..of loop. It is invoked in
every lane sync run, and the keep-lane flow may invoke it again after syncConfigFromMain() to
discover newly git-authored components.

scopes/git/ci/sync/context-drift-detector.ts[21-54]
scopes/git/ci/sync/lane-sync-executor.ts[777-803]
scopes/git/ci/ci.main.runtime.ts[918-928]

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

## Issue description
`detectContextDrift()` loops over all pending components with a serial `for..of` and several awaited operations per component. This makes the drift check runtime roughly proportional to the pending set size, and the check can run more than once in a lane sync.
## Issue Context
The drift detector is on the critical path of `bit ci sync` lane runs.
## Fix Focus Areas
- scopes/git/ci/sync/context-drift-detector.ts[21-55]
- scopes/git/ci/sync/lane-sync-executor.ts[777-803]
- scopes/git/ci/ci.main.runtime.ts[918-928]
### Suggested approach
- Convert the per-component work to bounded parallelism (e.g. `pMapPool` with `concurrentComponentsLimit()`), preserving best-effort semantics (per-component failure -> warn + treat as git-authored).
- If you need a second drift check after `syncConfigFromMain()`, consider limiting the second pass to only components that could have been affected by config sync (or otherwise avoid recomputing unchanged items), while keeping correctness.
- Validate that concurrent access patterns are safe for the workspace/scope APIs used (model load, workspace.get, consumerComponentToVersion).

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


To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread scopes/git/ci/ci.main.runtime.ts Outdated
luvkapur and others added 7 commits August 6, 2026 13:31
…ification scopes to the snap set

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…detection

legacyBitIds bypasses Snapping own local-only filtering; the detector must
subtract workspace.filter.byLocalOnly itself or a local-only dev edit gets
snapped via snapIds and then fails at export.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds convergeContextDrift on CiMain: tags exactly the drifted set with an
explicit id list, patch release, ignoreIssues scoped to blockers already on
the recorded heads, then exports. Wired into syncMain between the
checkoutByCLIValues step and driftFiles() so the .bitmap/lockfile bump rides
the existing commit + bit-sync/main flow. Dry-run detects and reports but
tags nothing.
…dry-run summary, tighten e2e convergence assertion

convergeContextDrift returns an additive detected flag so callers stop
string-matching a summary sentinel across the module boundary, and the
tag-returned-null anomaly gets its own distinguishable summary instead of
being reported as "no dependency-context drift". syncMain dry-run now
returns the would-converge line instead of a contradictory converged
summary when driftFiles sees no file diff. The main-convergence e2e cell
now asserts the actual convergence bump and the real push summary string,
instead of a check that was already true before any run.
…log line

The mid-run log for detected drift passes with either summary branch
syncMain returns through, so round 1s fix to the actual returned
would-converge line had no regression coverage until now.
@luvkapur luvkapur changed the title fix(ci): fail the status verification only on components the run snaps or tags feat(ci): sync consumes dependency-context drift; lane snaps scope to git-authored changes Aug 6, 2026
Comment thread scopes/git/ci/ci.main.runtime.ts Outdated
Comment thread scopes/git/ci/ci.main.runtime.ts
Comment thread scopes/git/ci/ci.main.runtime.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

…sync, report auto-snapped drift dependents

- add overrides (env-computed dep data) to DRIFT_FIELDS so a real engine bump
  classifies as drift instead of no-oping the feature; unit case added
- recompute drift after syncConfigFromMain and extend snapIds with any newly
  git-authored ids, so a component the config sync just changed is not missed
- report a drifted id that auto-snaps as a dependent of a snapped component,
  and fix the lane log line's wording to match
- fix the detected-but-nothing-taggable dry-run summary, neutral snapIds log
  wording, and the convergeContextDrift docstring's actual blocker-tolerance
  behavior
- correct the docs' tag message shape and lane auto-snap behavior; add a
  noop-cell assertion and drop leftover process vocabulary from an e2e comment
- trim comment narration added by this branch to ASD-STE100 style

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread scopes/git/ci/ci.main.runtime.ts
Comment thread scopes/git/ci/sync/context-drift-detector.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

…DS/extensions rationale

- auto-snap runs on a component that depends on a snapped one, not the reverse; fix the docs and
  the lane-sync-executor comment to say so (ci.main.runtime.ts's comment was already correct)
- replace the vacuous DRIFT_FIELDS/extensions rationale with the load-bearing case: bit deps set
  writes both extensions and overrides, so extensions must stay comparable for that change to
  classify as git-authored

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread scopes/git/ci/sync/context-drift-detector.ts Outdated
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

… drift checks

Qodo review fixes for #10574: export() no longer sweeps every staged
component, a file-order-only diff no longer misclassifies as drift,
the per-component drift check runs with bounded concurrency, and the
new drift-report lines use the shared CLI formatting toolkit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

consumer.isComponentModified aligns these before comparing; normalizePayload
must too, or a stale value on an old recorded Version reads as a file change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread scopes/git/ci/ci.main.runtime.ts
Comment on lines +943 to +946
if (snapIds) {
const { gitAuthored } = await this.detectContextDrift();
const known = new Set(snapIds.map((id) => id.toStringWithoutVersion()));
const missing = gitAuthored.filter((id) => !known.has(id.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.

Remediation recommended

2. Stale status scoping 🐞 Bug ◔ Observability

snapPrCommit() scopes workspace-status verification using the initial snapIds, but
snapAndExportReusingLane() can later expand snapIds after syncConfigFromMain(). This makes the
earlier “issues … do not block” output potentially incorrect and can defer tag-blocker failures to
the snap phase unexpectedly.
Agent Prompt
### Issue description
Workspace status verification is performed before lane config sync, using the initial `snapIds`. Later, `snapAndExportReusingLane()` may expand `snapIds` and pass the expanded set into `snapping.snap()`, meaning:
- the earlier status scoping/warning can be misleading (it may claim issues don’t block because they’re “not snapped”, but they later become snapped), and
- failures can shift from the status-check step to the snap step for components added later.

### Issue Context
The PR intentionally expands `snapIds` after `syncConfigFromMain()` because the config sync can make additional components become “git-authored”. That expansion needs to be reflected in any status scoping / messaging.

### Fix Focus Areas
- Re-run (or defer) `verifyWorkspaceStatusInternal()` until after `syncConfigFromMain()` and any `snapIds` expansion is complete, using the final `snapIds` that will be passed to `snapping.snap()`.
- If re-running is too expensive, at least avoid printing the “issues above … do not block” message when `snapIds` is not yet final.

#### Code references
- scopes/git/ci/ci.main.runtime.ts[465-477]
- scopes/git/ci/ci.main.runtime.ts[775-784]
- scopes/git/ci/ci.main.runtime.ts[939-948]
- scopes/git/ci/ci.main.runtime.ts[1064-1071]

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 31ae69b. When the post-config-sync recompute adds ids, the run re-verifies the workspace status scoped to the final snap set before the snap. Zero added ids cost no extra status pass.

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

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 09dc3a5

An id added after syncConfigFromMain never passed the scoped status
gate that ran before the expansion; re-run it over the final set.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread scopes/git/ci/ci.main.runtime.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 31ae69b

A non-blocker issue on an in-set component leaked its name into the
ignore union; scope the union to issues where isTagBlocker is true.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread scopes/git/ci/sync/main-config-sync.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 62d8e76

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