Skip to content

fix(perf): stop loading hard-link-directory on every bit invocation; dual-metric self-diagnosing fs-read guard - #10599

Merged
davidfirst merged 7 commits into
masterfrom
fix/fs-read-guard-status-diagnostics
Aug 26, 2026
Merged

fix(perf): stop loading hard-link-directory on every bit invocation; dual-metric self-diagnosing fs-read guard#10599
davidfirst merged 7 commits into
masterfrom
fix/fs-read-guard-status-diagnostics

Conversation

@luvkapur

@luvkapur luvkapur commented Aug 11, 2026

Copy link
Copy Markdown
Member

Why

e2e_test_bbit (nightly, 32 nodes) has been red since 2026-07-23 — 19 of the last 21 failures are one single test out of ~2,900: the bit status filesystem-read guard (MAX_FILES_READ_STATUS = 1515, actual 1532 → 1541 and creeping).

Root cause

Diffing BIT_DEBUG_READ_FILE output of bvm-installed 2.0.33 (last green) vs 2.0.35 (first red) — only two commits apart — shows zero new logical modules (1623 → 1623). Instead, #10515's dependency change reshaped the released bundle's pnpm hoisted layout: the hard-link-directory cluster (plus a private fs-extra tree) moved from a small nested copy under one package to a larger nested copy under the compiler package (net +21 physical reads, duplicate copies 212 → 233). The nightly +1–3 creep is the same mechanism — every nightly bundle re-resolves latest transitive deps, and hoisting shifts occasionally add nested duplicates.

Changes

  1. compiler.task.ts: lazy-import hard-link-directory — it is used only inside the build task, yet its top-level import pulled ~36 files into every bootstrap/bit status in the bundle layout. Verified the compiled dist defers the require until the task executes.
  2. The bit status guard now tracks two metrics instead of one raw count:
    • Unique logical modules (threshold 1700): paths normalized past the last node_modules/, so all copies of the same module count once. Catches bit loading more code, is immune to the hoisting-drift noise class above, and converges across install flavors — one snapshot (files-snapshot-status.txt) serves repo and bundle runs alike. Measured baselines: 1364 (repo), 1623–1645 (bundles).
    • Duplicate copies (ceiling 350): physical files minus logical modules. Catches bit loading redundant copies/versions of packages — a class the logical metric alone is blind to, and a core motivation for this test. Measured baselines: ~99 (repo), 212–233 (bundles); headroom accommodates ordinary hoisting drift.
  3. Failures are self-diagnosing, greens are self-measuring — a logical-modules red prints which modules are new vs the snapshot plus the full current list; a duplication red names the most-duplicated packages and the locations their copies load from; every run prints a total reads / physical files / logical modules / duplicate copies report line, accumulating true per-environment baselines in CI logs for tightening both thresholds from measurements.

Verification

  • npm run lint green (oxlint + tsc).
  • Snapshot-diff logic exercised against real captured outputs of 2.0.33 / 2.0.35 / 2.0.74; root cause re-reproduced independently from fresh bvm installs of 2.0.33/2.0.35.
  • The bit status e2e passes on the repo flavor (total reads 1616, physical 1463, logical 1364, duplicates 99); the duplication failure path verified by artificially lowering the ceiling.
  • bit compile teambit.compilation/compiler — dist verified lazy: await Promise.resolve().then(() => require('@teambit/toolbox.fs.hard-link-directory')).
…make fs-read guard self-diagnosing

The nightly e2e_test_bbit job has been red since 2026-07-23: the released
bundle's 'bit status' crossed the MAX_FILES_READ_STATUS=1515 guard (1532,
creeping to 1541 by 2.0.74). Root cause, found by diffing BIT_DEBUG_READ_FILE
output of bvm-installed 2.0.33 (last green) vs 2.0.35 (first red): #10515's
dependency change reshaped the bundle's pnpm hoisted layout, so
@teambit/toolbox.fs.hard-link-directory plus a private fs-extra tree load from
a nested copy under the compiler package instead of a shared hoisted copy
(~+21 reads). The nightly creep (+1-3) is the same mechanism - each nightly
re-resolves transitive deps and hoisting shifts add nested duplicates.

Three changes:
- compiler.task.ts imports hard-link-directory lazily inside the build task -
  it is build-time-only, yet its top-level import pulled ~36 files into every
  bootstrap/status in the bundle layout.
- the bit-status guard failure now prints WHICH files are new vs a snapshot
  (files-snapshot-status.txt, generated from the released 2.0.74 bundle),
  same as the bootstrap guard - a red is a 2-minute diagnosis, not a 3-week
  mystery. Layout-mismatched diffs (repo vs bundle flavor) degrade to a
  count + sample instead of noise.
- threshold 1515 -> 1650: headroom for bundle-layout drift, which the diff in
  the error message now makes visible and attributable.

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

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

Copy link
Copy Markdown

PR Summary by Qodo

Reduce status filesystem reads and add dual-metric diagnostics

🐞 Bug fix 🧪 Tests ✨ Enhancement 🕐 20-40 Minutes

Grey Divider

AI Description

• Defers build-only hard-link loading to avoid unnecessary startup filesystem reads.
• Separates logical-module growth from dependency-layout duplication in the status guard.
• Adds actionable failure diagnostics, continuous metrics, and a shared logical-module baseline.
Diagram

graph TD
  A["Bit invocation"] -->|loads| B["Compiler task"] -->|build only| C["Lazy hard-link import"]
  A -->|traces reads| D["Read trace"] --> E["Metric analyzer"] --> F["Logical guard"] --> H["CI diagnostics"]
  E --> G["Duplication guard"] --> H
Loading
High-Level Assessment

The combined approach is appropriate: defer the build-only dependency at its sole use site, then measure logical code growth separately from physical duplication. Simply raising the raw-read threshold or maintaining install-layout-specific snapshots would preserve hoisting noise and provide weaker diagnostics, while removing duplication checks would miss real redundant loading.

Files changed (3) +1814 / -18

Bug fix (1) +5 / -1
compiler.task.tsLazy-load hard-link support during compiler builds +5/-1

Lazy-load hard-link support during compiler builds

• Moves hard-link-directory from a top-level import into the artifact-linking build step. This prevents its dependency tree from loading during unrelated Bit invocations such as bit status.

scopes/compilation/compiler/compiler.task.ts

Tests (2) +1809 / -17
files-snapshot-status.txtAdd status logical-module baseline snapshot +1645/-0

Add status logical-module baseline snapshot

• Adds a 1,645-entry, install-layout-independent baseline of logical modules loaded by bit status. The guard uses it to identify newly loaded modules without treating pnpm hoisting paths as code growth.

e2e/performance/files-snapshot-status.txt

filesystem-read.e2e.tsReplace raw status read limit with dual performance guards +164/-17

Replace raw status read limit with dual performance guards

• Normalizes traced paths into logical module identifiers and independently limits logical growth and duplicate physical copies. It now logs all metrics on every run and emits focused module-diff or package-location diagnostics on failure.

e2e/performance/filesystem-read.e2e.ts

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

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

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Remediation recommended

1. Status snapshot seems mismatched ✓ Resolved 🐞 Bug ☼ Reliability
Description
files-snapshot-status.txt contains 1,865 entries, while the test comments describe the released
bundle as ~1,540 reads and the guard threshold is 1,650; this strongly suggests the snapshot was
generated from a different flavor/run than what the test documents. If/when the bit status guard
fails, getNewlyLoadedFiles() diagnostics can become misleading or miss the actual newly-loaded
files you expect it to highlight for that environment.
Code

e2e/performance/files-snapshot-status.txt[R1862-1865]

+lodash.compact/index.js
+@teambit/graph/dist/component-id-graph.js
+@teambit/status/dist/status-formatter.js
+@teambit/cli/dist/pager.js
Evidence
The test explicitly documents an expected released-bundle read count (~1,540) and sets a threshold
of 1,650, but the newly-added status snapshot file itself has 1,865 entries, indicating the snapshot
likely doesn’t represent the documented baseline/flavor and can therefore mislead diagnostics.

e2e/performance/filesystem-read.e2e.ts[14-35]
e2e/performance/filesystem-read.e2e.ts[85-99]
e2e/performance/files-snapshot-status.txt[1851-1865]

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

## Issue description
`files-snapshot-status.txt` appears inconsistent with the documented expected `bit status` read counts and the configured threshold, which can reduce the usefulness of the self-diagnosing diff when the guard trips.
### Issue Context
- The test’s `MAX_FILES_READ_STATUS` is set to 1650 and comments state the released/hoisted bundle is ~1540 reads.
- The committed `files-snapshot-status.txt` currently contains 1865 lines (entries).
- `getNewlyLoadedFiles(output, 'files-snapshot-status.txt')` uses this file only for diagnostics, so an overinclusive/mismatched snapshot makes failure output noisy or misleading.
### Fix Focus Areas
- e2e/performance/filesystem-read.e2e.ts[14-99]
- e2e/performance/files-snapshot-status.txt[1-1865]
### Suggested fix
1. Regenerate `files-snapshot-status.txt` using the same environment/flavor the comments describe (released bundle hoisted layout vs repo `.pnpm` layout), and ensure it corresponds to the expected baseline.
2. Align the inline documentation (expected counts) with the regenerated snapshot.
3. Optionally add a lightweight sanity check (comment or assertion) to prevent committing a status snapshot whose size is wildly inconsistent with the expected baseline/threshold.

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


Grey Divider

Tip of the day
💡 Did you know, you can start a comment with 'qodo' or '@qodo' to chat about any finding

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread e2e/performance/files-snapshot-status.txt
@luvkapur
luvkapur marked this pull request as draft August 11, 2026 19:53
luvkapur and others added 2 commits August 11, 2026 16:00
…ten threshold to 1600; print full file list on failure

Addresses the Qodo finding on #10599: the snapshot was generated from a local
macOS run of the released bundle - correct flavor, but a superset of what CI's
leaner fixture reads. Regenerated inside a linux/amd64 node:22.22.0 container
from the released linux-x64 bundle (1,878 entries; the mac and linux sets
share 1,860 of ~1,870 files, so flavor was never the issue - offline supersets
are inherent). Two hardenings follow from accepting that limit:

- the guard now calls makeSnapshot() on failure, printing the complete current
  install-file list, so every red carries the exact data to inspect masked
  files and regenerate a perfect snapshot from real CI output
- threshold 1650 -> 1600: the post-fix baseline is a projection (1541 - ~36
  lazy-import reads = ~1505) and drift events arrive in ~+20 lumps; 1600 =
  projection + error margin + two events. Re-tighten to measured + ~50 once
  post-fix nightlies establish the real baseline.

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

Replaces the threshold-bump approach. The raw-reads metric conflated two
things: bit loading more code (the regression the guard exists to catch) and
pnpm hoisting layout drift duplicating physical copies of the same modules
(packaging noise that reshuffles every nightly bundle). Measured across the
incident that motivated this: 2.0.33 -> 2.0.35 physical unique files went
1836 -> 1857 (dup copies 211 -> 232) while logical modules stayed exactly
1625 -> 1625 - the three-week nightly red was 100% duplication noise, 0% new
code.

The guard now counts unique logical modules (paths normalized past the last
node_modules/ segment), which:
- is immune to the entire layout-drift noise class - no headroom needed, the
  interim 1650/1600 bumps are gone
- converges for both install flavors (repo .pnpm layout and released hoisted
  bundle), so one snapshot serves both (files-snapshot-status.txt is now
  logical ids, 1,645 entries from the released linux-x64 bundle)
- still catches real regressions (verified: an injected module is reported
  by name; measured logical baselines 1625-1645 vs threshold 1700)

Every run also prints a physical/logical/duplication report line so CI logs
accumulate true per-environment baselines - the threshold tightens to
measured+~30 once those land, and duplication trends stay visible without
failing anyone. Bootstrap guard unchanged (stable, its own physical snapshot);
migrate it the same way once its CI report lines accumulate.

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

Copy link
Copy Markdown
Member

Added a second guard in 4874ced: the logical-module metric alone is blind to duplication — if bit starts loading two copies/versions of the same package (real extra I/O and memory), logical modules stay flat. Since duplication was a core motivation for this test, the guard now also fails when duplicate copies (physical files minus logical modules) exceed a ceiling (350; measured baselines: ~99 repo flavor, 211–233 released bundles). The failure output names the most-duplicated packages and the versioned locations their copies load from, e.g.:

fs-extra: 52 duplicate file read(s) from 3 locations:
  - .pnpm/fs-extra@10.0.0/
  - .pnpm/fs-extra@10.1.0/
  - .pnpm/fs-extra@11.4.0/

Also merged latest master into the branch. Verified: lint green; the bit-status e2e passes on the repo flavor (report line: total reads 1616, physical 1463, logical 1364, duplicates 99), and the duplication failure path renders correctly when the ceiling is artificially lowered.

@davidfirst davidfirst changed the title fix(perf): stop loading hard-link-directory on every bit invocation; self-diagnosing fs-read guard Aug 25, 2026
@davidfirst
davidfirst marked this pull request as ready for review August 25, 2026 19:34
@davidfirst
davidfirst enabled auto-merge (squash) August 25, 2026 19:35
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

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

@davidfirst
davidfirst merged commit 7548252 into master Aug 26, 2026
14 checks passed
@davidfirst
davidfirst deleted the fix/fs-read-guard-status-diagnostics branch August 26, 2026 14:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants