Skip to content

Instantly share code, notes, and snippets.

@R4wm
Created March 11, 2026 23:05
Show Gist options
  • Select an option

  • Save R4wm/68ad9be9a0927ea9c913514bce993159 to your computer and use it in GitHub Desktop.

Select an option

Save R4wm/68ad9be9a0927ea9c913514bce993159 to your computer and use it in GitHub Desktop.
Incident Report: vue-virtual-scroller silent upgrade breaks production feed (2026-03-11)

Incident Report: vue-virtual-scroller Silent Upgrade Breaks Production Feed

Date: 2026-03-11 Severity: High — core user interactions broken on production web app Time to Resolution: ~6 hours of investigation


Summary

A 3-year-dormant npm package (vue-virtual-scroller) published a new beta version (2.0.0-beta.9) on 2026-03-10. Due to a combination of semver caret ranges and a CI pipeline that deletes package-lock.json before every build, the new version was silently picked up on the next deployment. The new version is a complete ESM module rewrite that broke the DynamicScroller component, causing two major symptoms:

  1. Feed flickering on scroll — virtual list recycling broke, causing visible re-renders
  2. Comment textbox not opening — clicking the comment icon on feed/profile pages had no visible effect; component state was being lost during virtual DOM recycling

Timeline

Time Event
2026-03-10 ~13:59 UTC vue-virtual-scroller@2.0.0-beta.9 published to npm (first release in 3 years)
2026-03-10 CI build triggered — package-lock.json deleted, npm install resolves beta.9
2026-03-10 Deployment goes live with broken dependency
2026-03-11 Bug reports: comment icon not working, feed flickering
2026-03-11 ~14:00 Investigation begins — initially focused on Vue component logic, CSS, nginx config, Cloudflare caching
2026-03-11 ~17:30 Breakthrough: compared working deployment (2026-03-07) vs broken deployment (2026-03-10) CI logs
2026-03-11 ~17:45 Root cause identified — npm install output shows vue-virtual-scroller@2.0.0-beta.9 in broken build vs 2.0.0-beta.8 in working build
2026-03-11 ~18:00 Fix PR created — pin exact version 2.0.0-beta.8
2026-03-11 ~18:45 Fix verified locally (beta.9 reproduces bug, beta.8 resolves it)
2026-03-11 ~19:00 Fix deployed to development environment, confirmed working

Root Cause Analysis

Three factors combined to create the failure:

1. Semver Caret Range in package.json

"vue-virtual-scroller": "^2.0.0-beta.8"

The caret (^) allows npm to install any version >=2.0.0-beta.8 <3.0.0. This is standard practice for stable packages but dangerous for beta/pre-release dependencies where breaking changes are expected between versions.

2. CI Pipeline Deletes Lock File

The CI build script (build-and-deploy.yml) runs:

rm -rf node_modules package-lock.json
npm install

This forces fresh dependency resolution on every build. Without a lock file, npm resolves the latest compatible version per semver rules. When beta.9 appeared on the registry, the next build picked it up automatically.

3. Breaking Changes in beta.9

vue-virtual-scroller@2.0.0-beta.9 is a complete rewrite:

  • Converted from CommonJS to ESM modules
  • Rewrote the DynamicScroller component internals
  • Changed how virtual DOM recycling and item state management works

The DynamicScroller component is used in FeedTemplate.vue to render the main content feed. The rewrite broke:

  • Item state preservation — component state (like commentAccordion toggle) was lost during virtual scroll recycling, so clicking the comment icon appeared to do nothing
  • Scroll stability — items would flicker/re-render during scroll as the recycling logic behaved differently

Why Local Development Wasn't Affected

Local development machines had node_modules/vue-virtual-scroller already installed at 2.0.0-beta.8 from a previous npm install. Since developers don't delete node_modules and package-lock.json before every install, npm saw the existing version satisfied the semver range and kept it. The bug was only reproducible by:

  1. Deleting node_modules and running npm install (which would pull beta.9), or
  2. Explicitly installing beta.9: npm install vue-virtual-scroller@2.0.0-beta.9

This created a frustrating gap where everything worked locally but was broken in the deployed environment.


Fix

PR: Pin vue-virtual-scroller to an exact version (remove caret):

- "vue-virtual-scroller": "^2.0.0-beta.8"
+ "vue-virtual-scroller": "2.0.0-beta.8"

This single-character change prevents npm from auto-upgrading to any future versions.


Investigation Path (What Didn't Work)

Before identifying the root cause, several hours were spent investigating:

  1. Vue component logicUserEngagement.vue, PostDetails.vue, CreatePostPopup.vue — all correct
  2. API layer — verified comments API worked fine via curl
  3. Nginx configuration — found and fixed an unrelated PWA file-serving issue, but it didn't resolve the symptoms
  4. Cloudflare caching — investigated cache rules, purged cache — not the issue
  5. CSS/styling — reviewed scoped styles, no conflicts found
  6. Event tracking library — diffed vue-event-tracking v2.0.0 vs v2.0.1 — only added payload fields, no click interception

The breakthrough came from directly comparing CI build logs between a known-good deployment and the broken deployment. The npm install output clearly showed different resolved versions of vue-virtual-scroller.


Lessons Learned

1. Never delete package-lock.json in CI

The lock file exists to guarantee reproducible builds. Deleting it defeats this purpose and exposes every build to upstream changes. The CI script should be changed from:

rm -rf node_modules package-lock.json
npm install

To:

npm ci

npm ci uses the lock file for deterministic installs and is faster (it deletes node_modules itself and installs from scratch based on the lock file).

2. Pin pre-release dependencies

Semver caret ranges are reasonable for stable packages that follow semver conventions. But beta/pre-release packages can (and do) introduce breaking changes between versions. Always pin exact versions for pre-release dependencies:

"some-beta-package": "2.0.0-beta.8"    // ✅ exact
"some-beta-package": "^2.0.0-beta.8"   // ❌ allows upgrades

3. Compare CI artifacts early

When local works but deployed doesn't, the first step should be comparing the exact build artifacts — CI logs, dependency trees, bundle contents. This would have saved several hours of investigating application code that was never the problem.

4. Audit dormant dependencies

A package with no releases for 3 years suddenly publishing a major rewrite is a red flag. Consider:

  • Forking critical dependencies that are unmaintained
  • Using npm audit and dependency monitoring tools
  • Setting up alerts for unexpected dependency changes

Package Details

  • Package: vue-virtual-scroller
  • Maintainer: Guillaume Chau (Akryum), Vue.js core team member
  • beta.8 published: 2023-02-06
  • beta.9 published: 2026-03-10 (1,128 days later)
  • Change: Complete ESM rewrite of the virtual scrolling library
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment