Skip to content

fix(ci): drive linkinator via its API to end the exit-13 link-check flake#62

Merged
jdevalk merged 1 commit into
mainfrom
fix/links-programmatic-api
Jun 27, 2026
Merged

fix(ci): drive linkinator via its API to end the exit-13 link-check flake#62
jdevalk merged 1 commit into
mainfrom
fix/links-programmatic-api

Conversation

@jdevalk

@jdevalk jdevalk commented Jun 27, 2026

Copy link
Copy Markdown
Owner

What

Replaces the npx linkinator CLI call in the Links workflow with a small programmatic-API script, scripts/check-links.mjs, and pins linkinator as a devDependency.

Why

linkinator@6's CLI runs await main() at the top level. On Node 22 the event loop can empty while that await is still pending, aborting with exit 13 ("unsettled top-level await") after fetching only /. That's a deterministic tooling crash, not a broken link — and the earlier retry-on-13 hardening (#58/#59) couldn't reliably paper over it: the Internal links check still went red on PRs #60 and #61.

The script drives linkinator's LinkChecker class directly and resolves with .then() / process.exit instead of a top-level await, so our own process can't hit the same foot-gun. Real broken links still exit 1; a crash exits 2.

Details

  • Internal job (--internal): skips every off-host link, same as before.
  • External job (--config linkinator.config.json): the config's skip list is mapped to the API's linksToSkip; recurse, concurrency, timeout, retry, retryErrors, retryErrorsCount pass straight through.
  • Both jobs still serve the built site with astro preview --port 4321 and point the script at it.

Verification

Built, served, and ran locally against the preview server:

Scanned 1500 links (1289 skipped) — 0 broken.
EXIT: 0

No exit-13 crash. npm run lint and prettier --check pass.

🤖 Generated with Claude Code

…lake

The links job shelled out to `npx linkinator`, whose CLI runs `await main()`
at the top level. On Node 22 the event loop can empty while that await is
still pending, aborting with exit 13 ("unsettled top-level await") after
fetching only `/` — a deterministic crash, not a broken link, that the
earlier retry-on-13 hardening (#58/#59) could not reliably paper over (it
still failed on PRs #60 and #61).

Replace the CLI with scripts/check-links.mjs, which drives linkinator's
LinkChecker class programmatically and resolves with `.then()`/process.exit
so our process can't hit the same foot-gun. Pin linkinator as a devDependency
for a stable version. Both jobs (internal --internal, external --config) use
it; the config's `skip` list maps to the API's `linksToSkip`.

Verified locally: builds, serves, and scans 1500 internal links (0 broken,
exit 0) with no exit-13 crash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying specification-website with  Cloudflare Pages  Cloudflare Pages

Latest commit: 6545ac4
Status: ✅  Deploy successful!
Preview URL: https://d781edf8.specification-website.pages.dev
Branch Preview URL: https://fix-links-programmatic-api.specification-website.pages.dev

View logs

@jdevalk jdevalk merged commit 77348c5 into main Jun 27, 2026
8 checks passed
@jdevalk jdevalk deleted the fix/links-programmatic-api branch June 27, 2026 11:43
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