Skip to content

fix: use buffered observer in agent return path for LCP and CLS snippets#74

Merged
nucliweb merged 3 commits into
mainfrom
fix/agent-sync-return-lcp-cls
Apr 29, 2026
Merged

fix: use buffered observer in agent return path for LCP and CLS snippets#74
nucliweb merged 3 commits into
mainfrom
fix/agent-sync-return-lcp-cls

Conversation

@nucliweb
Copy link
Copy Markdown
Owner

Finding

performance.getEntriesByType("largest-contentful-paint") and "layout-shift" always return an empty array in Chrome unless a PerformanceObserver has been actively subscribed. This is a Chrome implementation detail: these entry types are observer-only and are not written to the shared performance timeline buffer.

Impact

The "Synchronous return for agent" section at the bottom of these snippets was silently returning { status: "error", error: "No LCP entries yet" } in every automated context:

  • Chrome DevTools MCP (mcp__chrome-devtools__evaluate_script)
  • WebPerf Skills via CDP
  • The new CLI (discovered during CLI development — the CLI works around it via an addInitScript shim, but the root cause is in the snippets)

Manual DevTools console usage was not affected because the preceding observer registration happens to populate a context-local buffer that the synchronous read can sometimes see on long-lived pages, but this was never reliable.

Verification

// Chrome returns [] for these, even after observer.observe({ buffered: true }):
performance.getEntriesByType("largest-contentful-paint") // []
performance.getEntriesByType("layout-shift")             // []

// Only a PerformanceObserver callback delivers the entries:
new PerformanceObserver((list) => list.getEntries()) // ✓ entries here
  .observe({ type: "largest-contentful-paint", buffered: true });

Fix

  • Changed (() => {(async () => { in the 3 affected snippets.
  • Replaced getEntriesByType with a short-lived PerformanceObserver that resolves after setTimeout(0) (guarantees all buffered microtasks have flushed).
  • Chrome DevTools console is unaffected: modern Chrome auto-awaits async IIFEs and displays the resolved value inline.
  • page.evaluate() (Playwright/CDP) is unaffected: it already awaits Promises.

Affected snippets

Snippet Entry type fixed
LCP.js largest-contentful-paint
CLS.js layout-shift
LCP-Sub-Parts.js largest-contentful-paint

"resource" and "navigation" entries used in LCP-Sub-Parts.js work correctly via getEntriesByType and are unchanged.

Test plan

  • Run LCP.js via Chrome DevTools MCP evaluate_script on a live page — result should be { status: "ok", value: <ms>, ... } not { status: "error" }.
  • Same for CLS.js and LCP-Sub-Parts.js.
  • Run each snippet interactively in Chrome DevTools console — should display the resolved object (Chrome auto-awaits).
  • Confirm LCP-Video-Candidate.js is unaffected (it already used a proper observer pattern).

Chrome does not expose largest-contentful-paint or layout-shift entries via
performance.getEntriesByType() without an active PerformanceObserver. The
previous sync return path silently returned "No LCP entries yet" in any
automated context (Chrome DevTools MCP, CLI, Skills via CDP).

Fix: make the IIFE async and collect buffered entries via a short-lived
PerformanceObserver, resolving after a setTimeout(0) to ensure buffered
callbacks have flushed.

Affected snippets: LCP.js, CLS.js, LCP-Sub-Parts.js

Chrome DevTools console is unaffected — it auto-awaits async IIFEs and
displays the resolved value.
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
webperf-snippets Ready Ready Preview, Comment Apr 29, 2026 6:13pm
webperf-snippets-u6am Ready Ready Preview, Comment Apr 29, 2026 6:13pm

@nucliweb nucliweb merged commit 4728f69 into main Apr 29, 2026
4 checks passed
@nucliweb nucliweb deleted the fix/agent-sync-return-lcp-cls branch April 29, 2026 18:14
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