From f6f7cac4037eae3f7fb54b6caa8cb0c407fa1c5f Mon Sep 17 00:00:00 2001 From: svemagie <869694+svemagie@users.noreply.github.com> Date: Thu, 19 Mar 2026 22:28:40 +0100 Subject: [PATCH] fix: harden unfurl timeout + persist CI fetch cache - Add hard 22s Promise.race deadline in prefetchUrl() to guard against TCP-level connection hangs that bypass unfurl.js's read-only timeout. Fixes builds hanging indefinitely on unresponsive hosts. - Add actions/cache step to deploy.yml persisting .cache/ between runs. Prevents webmention (and all eleventy-fetch) data loss on transient 502s: a populated cache means failures return existing data, not []. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/deploy.yml | 8 ++++++++ lib/unfurl-shortcode.js | 13 +++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 94e8d18..3699462 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -32,6 +32,14 @@ jobs: "sudo bastille cmd node cat /usr/local/indiekit/content/.indiekit/homepage.json" \ > content/.indiekit/homepage.json + - name: Cache Eleventy fetch cache + uses: actions/cache@v4 + with: + path: .cache + key: eleventy-fetch-${{ runner.os }}-${{ hashFiles('package-lock.json') }} + restore-keys: | + eleventy-fetch-${{ runner.os }}- + - name: Build CSS run: npm run build:css diff --git a/lib/unfurl-shortcode.js b/lib/unfurl-shortcode.js index 7494a5b..ff8ba93 100644 --- a/lib/unfurl-shortcode.js +++ b/lib/unfurl-shortcode.js @@ -132,10 +132,15 @@ export async function prefetchUrl(url) { const metadata = await throttled(async () => { try { - return await unfurl(url, { - timeout: 20000, - headers: { "User-Agent": USER_AGENT }, - }); + // Hard outer deadline guards against TCP-level hangs that bypass unfurl's + // own timeout (which only covers HTTP read, not connection establishment). + const deadline = new Promise((_, reject) => + setTimeout(() => reject(new Error("hard deadline 22s")), 22000) + ); + return await Promise.race([ + unfurl(url, { timeout: 18000, headers: { "User-Agent": USER_AGENT } }), + deadline, + ]); } catch (err) { console.warn(`[unfurl] Failed to fetch ${url}: ${err.message}`); return null;