From 6cd9e7eb3c8a68e55b110a2b6655fa1f6354a75a Mon Sep 17 00:00:00 2001 From: Devin Foley Date: Fri, 3 Apr 2026 09:06:49 -0700 Subject: [PATCH] fix: address Greptile P1 findings on browser lifecycle - Validate URL and compute origin before launching browser so invalid URLs fail fast without leaking a Chromium process - Wrap entire browser lifecycle (context, page, route setup) inside try-finally so browser.close() is always called - Require apiBase in loadBoardToken to prevent undefined URL construction Co-Authored-By: Paperclip --- scripts/screenshot.cjs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/scripts/screenshot.cjs b/scripts/screenshot.cjs index 141468f9466..cf405406160 100644 --- a/scripts/screenshot.cjs +++ b/scripts/screenshot.cjs @@ -44,7 +44,7 @@ function loadBoardToken() { const auth = JSON.parse(fs.readFileSync(authPath, "utf-8")); const creds = auth.credentials || {}; const entry = Object.values(creds)[0]; - if (entry && entry.token) return { token: entry.token, apiBase: entry.apiBase }; + if (entry && entry.token && entry.apiBase) return { token: entry.token, apiBase: entry.apiBase }; } catch (_) { // ignore } @@ -60,23 +60,25 @@ if (!cred) { // Resolve URL — if it starts with / treat as path relative to apiBase const url = rawUrl.startsWith("http") ? rawUrl : `${cred.apiBase}${rawUrl}`; +// Validate URL before launching browser +const origin = new URL(url).origin; + // --- Screenshot ---------------------------------------------------------- (async () => { const { chromium } = require("playwright"); const browser = await chromium.launch(); - const origin = new URL(url).origin; - const context = await browser.newContext({ - viewport: { width, height }, - }); - - const page = await context.newPage(); - // Scope the auth header to the Paperclip origin only - await page.route(`${origin}/**`, (route) => { - route.continue({ - headers: { ...route.request().headers(), Authorization: `Bearer ${cred.token}` }, - }); - }); try { + const context = await browser.newContext({ + viewport: { width, height }, + }); + + const page = await context.newPage(); + // Scope the auth header to the Paperclip origin only + await page.route(`${origin}/**`, (route) => { + route.continue({ + headers: { ...route.request().headers(), Authorization: `Bearer ${cred.token}` }, + }); + }); await page.goto(url, { waitUntil: "networkidle", timeout: 20000 }); await page.waitForTimeout(waitMs); await page.screenshot({ path: outPath, fullPage: false });