heygen-com/hyperframes · error
Website capture blocked: the loaded page matched an access-p
Error message
Website capture blocked: the loaded page matched an access-protection response (${statusDetail}, title ${JSON.stringify(evidence.title)}, ${evidence.textLength} text chars). The site may reject automated or data-center traffic; retry from an allowed network or provide source assets directly. What it means
Thrown by the website-capture pipeline after Puppeteer navigation when the loaded page is classified as an access-protection response (bot-challenge, WAF block, interstitial). The deciding function inspects httpStatus plus page evidence (title, textLength, bodyChildCount, hasChallengeElement); when it returns a blockedReason, the 'navigation' phase is marked degraded/blocked and this error fires. It is distinct from the soft 'very little text' warning that fires afterward for client-rendered SPAs.
Source
Thrown at packages/cli/src/capture/index.ts:311
"post-navigation content check timed out; continuing with HTTP-status blocked-page detection only";
warnings.push(message);
progress("warn", message);
}
const blockedReason = detectBlockedPage({
httpStatus: navigationResponse?.status() ?? null,
...(contentCheckTimedOut
? {
title: "",
textLength: 0,
bodyChildCount: 0,
hasChallengeElement: false,
}
: pageContentCheck),
});
if (blockedReason) {
phase("navigation", "degraded", "blocked");
throw new Error(blockedReason);
}
phase("navigation", "completed");
phase("core-extraction", "started");
if (!contentCheckTimedOut && pageContentCheck.textLength < 100) {
const reason =
"Page has very little text content (" +
pageContentCheck.textLength +
" chars) — may be blocked or a client-rendered SPA that needs more time";
warnings.push(reason);
progress("warn", reason);
}
const lazyLoadBudgetMs = Math.min(15_000, remainingMs());
const lazyScroll = await lazyScrollForCapture(page1, lazyLoadBudgetMs, {
onWarning: (message) => {
warnings.push(message);View on GitHub (pinned to c2996c8626)
Solutions
- Retry the capture from an allowlisted network (residential IP, allowed data center, or corporate network) or run a local capture and point the CLI at local HTML instead.
- If the site is yours, allowlist the runner's egress IP or User-Agent, or disable the bot rule for the capture path.
- Bypass capture entirely: provide source assets directly (screenshots/HTML) via the relevant --source / assets flags so no live navigation runs.
- Increase navigation wait / disable challenge wait only if you control the site and know the challenge is benign.
- Confirm it is actually a block and not a transient outage: re-run once, then inspect the statusDetail + title in the message to classify the protection vendor.
Example fix
// before: capture from a blocked data-center runner hyperframes product-launch https://app.example.com // after: capture locally on an allowlisted network, or hand in assets hyperframes product-launch ./local-app-snapshot --source assets/
Defensive patterns
Strategy: fallback
Validate before calling
// Pre-flight: HEAD the target from the runner's egress to anticipate a block
async function looksBlocked(url: string): Promise<boolean> {
const res = await fetch(url, { redirect: 'follow' });
const blocked = [401, 403, 429, 503].includes(res.status);
const ct = res.headers.get('content-type') ?? '';
return blocked || ct.includes('text/html') && /challenge|captcha|datadome|cloudflare/i.test(await res.text());
} Try / catch
try {
await captureSite(url);
} catch (err) {
if (/Website capture blocked/.test(String(err?.message))) {
// Fall back to user-provided source assets instead of live capture
return captureFromAssets(localAssetDir);
}
throw err;
} Prevention
- Allowlist the capture runner's egress IP with the target site's WAF/bot-protection vendor.
- For sites you do not control, prefer supplying source assets directly over live capture.
- Run capture from a residential/allowlisted network in CI, not arbitrary cloud egress.
When it happens
Trigger: Capturing a site whose response trips the blocker heuristics: a Cloudflare/Datadome/PerimeterX challenge page, a 403/429/503 from a data-center IP, or a login wall returning minimal body content. Also when contentCheckTimedOut is true the evidence is zeroed, which can still trip the classifier if the status itself looks protected.
Common situations: Running hyperframes capture from CI / a cloud VM whose egress IP is on a blocklist; capturing a site behind corporate SSO; a target that geo-blocks the region the runner is in; an ephemeral challenge that appears only under headless Chrome.
Related errors
- [build-zip] npm install into staging failed (status ${result
- Model download failed: ${model}
- --zoom selector matched no element: ${target.selector}
- stage budget exceeded while ${label} (${budgetMs}ms)
- OpenRouter request failed with HTTP ${res.status}
AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12).
Data as JSON: /api/errors/064b975a590370a2.
Report an issue: GitHub.