feat: retry and time out external link fetches - #185
Draft
claude[bot] wants to merge 2 commits into
Draft
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
claude
Bot
force-pushed
the
robust-external-link-fetching
branch
from
July 10, 2026 00:37
f459c7c to
f774618
Compare
claude
Bot
force-pushed
the
robust-external-link-fetching
branch
from
July 10, 2026 00:52
f774618 to
fcf8197
Compare
Retry external link fetches up to 3 times with backoff and a hard 10s wall-clock abort per attempt, since undici's per-phase timeouts are idle-based and don't catch slow-drip responses or redirect stalls. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NYcfm99SSXPiHtUNhBD71R
claude
Bot
force-pushed
the
robust-external-link-fetching
branch
from
July 10, 2026 01:06
fcf8197 to
cb16bed
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by David Sanders · Slack thread
Before: a single stalled external link could hang
lint-roller-markdown-links --fetch-external-linksfor minutes and flake CI. In this scheduled run, fetching https://www.electronjs.org/docs/api/browser-window hung past the 60s test timeout.After: each link fetch attempt is hard-aborted after 10s of wall-clock time and retried up to 3 times with backoff, so a stalled link fails fast (worst case ~43.5s per link) instead of hanging the check.
How
fetchExternalLinknow uses a small manual retry loop: up to 4 attempts total (1 + 3 retries) with 500ms/1s/2s backoff between attempts. It retries on any thrown error and on 429 or >= 500 responses.AbortSignal.timeout(10_000)— this is the load-bearing guard. undici's connect/headers/body timeouts are idle-based, so a redirect chain that stalls mid-hop or a server that slowly drips bytes never goes idle long enough to trip them (which is how the flake survived an earlier per-phase-timeouts-plus-RetryAgentapproach). A wall-clock abort per attempt covers connect hangs, mid-redirect stalls, and slow-drip bodies alike, and turns them all into retryable errors.Agent(5s connect, 10s headers, 10s body) so common failures still fail fast; noRetryAgentor error-code lists needed since the loop retries on anything thrown.--check-redirectstest can no longer be outlived by a single hung link.Broken link <url>, and a persistent non-OK response is returned and reported with its HTTP status.enginesis bumped to>=20.18.1 || >=22.4.0(per maintainer request) and the CI test matrix entry moves from Node 20.16 to 20.18.1.node:httpserver tests: one where the server destroys the socket on the first two requests and returns 200 on the third (retry on connection errors), and one where the server accepts the first connection but never responds at all, then returns 200 (hard per-attempt abort recovers the hang; runs in ~11s: 10s abort + backoff + fast success). These required a small asynccp.spawn-based test helper, since the existingspawnSynchelper blocks the event loop and an in-process server couldn't respond.--check-redirectstest now also runs against a local server (307 -> 200) instead of live-fetching electronjs.org: the CI stalls turned out to be region-scoped egress hangs on some Azure runners that persist past 60s, so no retry policy can save a test that requires a live fetch to succeed.tests/fixtures/redirected-external-link.mdis removed since nothing else referenced it.Canary workflow (debugging)
This PR also adds
.github/workflows/canary-external-links.yml, an hourly region-sampling canary for debugging the westcentralus hang. Each run spins up 3 parallelubuntu-latestjobs (independent region draws); each job records the runner's Azure region via IMDS and then probes https://www.electronjs.org/docs/api/browser-window ~20 times with three client shapes — curl over HTTP/2 with curl's default UA, curl over HTTP/1.1 with the exact header setlint-markdown-linkssends, and Node's globalfetchwith the same headers — capturing per-phase timings (DNS / TCP / TLS / TTFB / redirect / total) so the next time a probe lands on an affected runner we see exactly which phase stalls and for which client fingerprints. Full--trace-time -voutput is kept only for slow samples (>5s) or errors; each job writes a region + timing table to its step summary. Probe failures never fail the job.Since
schedule/workflow_dispatchonly fire for workflows on the default branch, the canary also has apull_requesttrigger so it samples runners on every push to this PR (and can be re-run from the checks UI for more draws). It's debugging scaffolding — easy to delete once the question is answered.Note: this will conflict with #163, which touches the same function — happy to rebase this on top of it or stack it, whichever is preferred.
🤖 Generated with Claude Code
https://claude.ai/code/session_01NYcfm99SSXPiHtUNhBD71R
Generated by Claude Code