stardust: a page with a CSS divide-by-zero (33,554,432 px layout) crashes full-page screenshots — crawl.mjs hides it, stitch-shot.mjs would too
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 35/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- javascript, nodejs
- Domain
- tooling
Research direction
Start with the screenshot try/catch in skills/extract/scripts/crawl.mjs, where fullPage: true falls back to a viewport capture on the same page after a crash. The reproduction is probe.mjs, run under a memory limit against the URL in the issue. Done means the crawl measures page size before capturing, skips the full-page capture when the layout overflows, reports such pages as a warning rather than OK, and stitch-shot.mjs has a height cap. Fixing crawl.mjs alone does not cover the other capture scripts the issue lists.
Written by the indexing model from the issue text.
Description
Summary
One page breaks every full-page screenshot in stardust. Its own CSS divides by zero, so Chrome lays the page out at 33,554,432 px, the layout maximum, in both directions. A full-page screenshot then takes several GB of memory. On a 12 GiB OF1 Labs container the VM hangs. On a laptop the Chrome tab crashes. Four production Replica runs for this site were lost on 2026-11-13.
crawl.mjs makes it worse in three ways:
- It never checks the page size before the screenshot.
- Its viewport fallback retries on the tab that crashed.
- It then reports the page as
OK, so the agent retries it.
The page
https://www.fondazionemenarini-minuti.com/ed-sc-prev/ is a draft copy of "Edizione Scientifica". It is listed in the WordPress sitemap but not linked from any menu.
Its Elementor stylesheet wp-content/uploads/elementor/css/post-7347.css has two widgets with a column count of 0:
.elementor-element-4664162 .jet-smart-listing__posts .jet-smart-listing__post-wrapper { flex: 0 0 calc( 100% / 0 ); max-width: calc( 100% / 0 ); }
.elementor-element-9191ba7 .jet-smart-listing__posts .jet-smart-listing__post-wrapper { flex: 0 0 calc( 100% / 0 ); max-width: calc( 100% / 0 ); }
100% / 0 is infinite, and Chrome clamps it to 33,554,432 px. The card's flex: 1 0 100% thumbnail and the image's aspect ratio (1024 / 471) carry that width down. The page grows to the same height.
In a 1440 × 900 window:
| value | |
|---|---|
scrollWidth × scrollHeight |
33554432 × 33554432 |
.jet-smart-listing__post-wrapper |
33,554,432 × 12,737,285 |
img.jet-smart-listing__post-thumbnail-img |
25,501,368 × 11,730,630 |
Proof: rewrite that one value to 100% at load time (context.route on post-7347.css). The page then measures 1440 × 2,862. The full-page screenshot takes 283 ms, and the tab peaks at 327 MB. Other pages of the site are normal: / is 2,328 px tall, /edizione-artistica/ is 4,182 px and /ricerca-arte/ is 12,074 px.
What happens
skills/extract/scripts/crawl.mjs has the same code at cd00021f, 8d2695d7 and main 4e420deb:
try {
await page.screenshot({ path: shotPath, fullPage: true, timeout: 30000 });
} catch {
screenshotMode = 'viewport';
try { await page.screenshot({ path: shotPath, fullPage: false, timeout: 30000 }); }
catch { screenshotMode = 'failed'; }
}
OF1 Labs Replica image (Chrome stable, channel: 'chrome', 8 GiB limit):
used MiB 1873
used MiB 5189 <- page.screenshot() started
used MiB 8434
shot fail page.screenshot: Protocol error (Page.captureScreenshot): Unable to capture screenshot
On a 12 GiB standard-4 container:
| run | memory | crawl step |
|---|---|---|
| e4c4a141 | 2.1 → 11.94 GiB at 11:42, stuck for hours | recapture of 5 pages incl. /ed-sc-prev/, --wait slow |
| ffc64803 | 2.2 → 11.92 GiB at 13:01, stuck 47 min | first crawl of 100 pages, --concurrency 3 |
| 57351e1b | 2.6 → 11.93 GiB at 13:28, ~20 min | one-page recrawl of /ed-sc-prev/, --wait slow |
| af04d2fc | 2.1 → 11.84 GiB at 13:35, stuck | one-page crawl2 of /ed-sc-prev/ |
In 57351e1b the first crawl's screenshot failed fast (screenshotMode: "failed"). The vision gate marked the page "suspect", the agent recrawled it alone, and the retry took the VM. The run-bg timeout (600 s) killed it. In the other three runs nothing ran any more, the Replica heartbeat stopped, and OF1 Labs failed the runs as lost_container.
macOS, system Chrome, one-page crawl (STARDUST_LIVE_STATE=0, fresh profile, RSS of the process tree sampled every second):
| peak | |
|---|---|
| Chrome renderer process for the page | 3.8 GB (a second run: 7.4 GB) |
| whole crawl process tree | 4.7 GB |
| same page with the CSS fixed | 327 MB |
The renderer ran at 98 % CPU for about 60 s. A Playwright page.on('crash') handler fired: the renderer died. Then:
- The
fullPage: falsefallback failed too, because it reused the dead page. - The crawl still printed
[crawl] OK ed-sc-prev,1/1 captured, 0 failedand exited 0. - The only trace is
screenshot: nulland_signals.screenshotMode: "failed"in the page record._crawl-log.json#crawl.failuresis empty.
On a fresh page, a viewport screenshot of the same URL works (51 ms, 349 MB peak).
Repro
// node probe.mjs https://www.fondazionemenarini-minuti.com/ed-sc-prev/
import { chromium } from 'playwright';
const b = await chromium.launch({ headless: true, channel: 'chrome' });
const p = await (await b.newContext({ viewport: { width: 1440, height: 900 } })).newPage();
p.on('crash', () => console.log('renderer crashed'));
await p.goto(process.argv[2], { waitUntil: 'load', timeout: 120000 });
console.log('size', await p.evaluate(() => [document.documentElement.scrollWidth, document.documentElement.scrollHeight]));
await p.screenshot({ path: '/tmp/s.png', fullPage: true, timeout: 60000 })
.then(() => console.log('shot ok'), (e) => console.log('shot fail', e.message.slice(0, 200)));
await b.close();
Run it under a memory limit (docker run --memory=8g …) and watch free -m, or watch the renderer's RSS on a desktop.
Proposal
-
Measure before the screenshot, and skip the full page when the layout is broken.
const MAX_SHOT_HEIGHT = 30000; // px const { w, h } = await page.evaluate(() => ({ w: document.documentElement.scrollWidth, h: Math.max(document.documentElement.scrollHeight, document.body?.scrollHeight ?? 0), })); rec._signals.pageSize = { w, h }; const overflow = h >= 33554432 || w > viewport.width * 4; if (overflow) rec._signals.layoutOverflow = true; if (overflow || h > MAX_SHOT_HEIGHT) { /* viewport screenshot, or a stitched one capped at MAX_SHOT_HEIGHT */ } else { /* fullPage as today */ }A
clipalone does not cap the height.page.screenshot({ clip: { x: 0, y: 0, width: 1440, height: 30000 } })withoutfullPage: truereturns a 1440 × 900 image, because Playwright clips to the viewport. That was tested on this page (68 ms, no memory spike). So either record it asscreenshotMode: 'viewport', or capture the capped height as scrolled chunks (asstitch-shot.mjsdoes), up toMAX_SHOT_HEIGHT. -
Make the fallback survive a crash. Listen for
page.on('crash'). Run any fallback screenshot on a new page in a new context, never on the page that threw. -
Report the failure. A page with
screenshotMode: 'failed'orlayoutOverflow: trueis aWARNline in the crawl output, notOK. Add it to_crawl-log.json#crawl.failures(or adegradedlist) with the reason. The vision gate can then show "layout overflow: CSS defect on the source", not "suspect", which invites a retry. -
Use one shared size guard in every capture script. The same unguarded
fullPage: trueappears in:diff/scripts/visual-diff.mjsdeploy/scripts/render-harness.mjsqa/scripts/checks/browse.mjsreskin/scripts/capture-content.mjsreskin/scripts/donor-probe.mjsspec/scripts/spec-capture.mjsspec/scripts/spec-sheet.mjs
replica/scripts/stitch-shot.mjsneeds it too, although it does not usefullPage. It scrollswhile (y < totalH)in 900 px chunks and callsstitchRows, which allocatesBuffer.alloc(width * totalH * 4). AttotalH = 33554432that is about 37,000 chunks and a buffer of about 193 GB. This comes from reading the code. It was not run on this page.gate.shtakes every live capture with this script. So the fidelity gate hits the page even with the crawl fixed. A shared helper indiff/scripts/live-session.mjscan serve all of them. -
The style census passed.
style-census.mjsmeasured this page at 1440 (17 s) and 360 (18 s) with no error.
Its memory use was not sampled.
For a replica of this page
The source page itself is broken: a visitor's browser lays it out the same way. A replica must not copy the bug. That needs an inconsistency-register entry ("render the listing at 1 column, not 100% / 0"), or the page leaves the roster as a draft.
On the OF1 Labs side
Labs is adding a memory guard to the Replica image (earlyoom, Chrome killed first) and replacing a hung container. With those, this page costs a killed browser instead of the run. The page still gets no screenshot, and today the agent still retries it, so the fixes above are still needed.
- Dominant language
- JavaScript
- Stars
- 324
- Forks
- 99
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 121
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from adobe/skills
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
Similar issues
-
nip27: long hashtag swallows the rest of the content, lone # gives an empty hashtagPossibly taken @bardrop claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
nbd-wtf/nostr-tools#578 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 82/100
h5bp/html5-boilerplate#3512 ·
-
documentation
Difficulty 1/5 1-3 hours Newbie friendliness 85/100
githubnext/gh-aw-workshop#5752 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 70/100
Maintainers usually reply within 1 day