CloakHQ/CloakBrowser · error · Error

CLOAKBROWSER_BINARY_PATH set to '${localOverride}' but file

Error message

CLOAKBROWSER_BINARY_PATH set to '${localOverride}' but file does not exist

What it means

RuntimeError('Viewport size not available') is raised in human_scroll_into_view when the resolved viewport dict is falsy or lacks a 'height' key — e.g. _VIEWPORT_JS returned an empty/None result, or the caller-supplied viewport was {'width': 0} or None. The scroller cannot compute geometry without a viewport height, so it aborts rather than guess.

Source

Thrown at js/src/download.ts:85

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

/**
 * Ensure the stealth Chromium binary is available. Download if needed.
 * Returns the path to the chrome executable.
 */
export async function ensureBinary(
  licenseKey?: string,
  browserVersion?: string,
  releaseChannel?: string,
): Promise<string> {
  // Check for local override
  const localOverride = getLocalBinaryOverride();
  if (localOverride) {
    if (!fs.existsSync(localOverride)) {
      throw new Error(
        `CLOAKBROWSER_BINARY_PATH set to '${localOverride}' but file does not exist`
      );
    }
    console.log(`[cloakbrowser] Using local binary override: ${localOverride}`);
    return localOverride;
  }

  const requestedVersion = normalizeRequestedVersion(browserVersion);

  // Pro license key check (custom download URL overrides Pro path)
  const key = resolveLicenseKey(licenseKey);
  const effectiveKey = process.env.CLOAKBROWSER_DOWNLOAD_URL ? undefined : key;
  if (effectiveKey) {
    const info = await validateLicense(effectiveKey);
    if (info?.valid) {
      // Free tier always gets the latest build. Drop any version pin: the server
      // force-serves latest to free keys, so fetching a pinned version's signed
      // manifest would mismatch the served bytes and fail checksum verification.

View on GitHub (pinned to d6bad5de26)

Solutions

  1. Wait until the page has laid out (domcontentloaded plus a real document, not about:blank) before scrolling.
  2. Launch with an explicit viewport so the value comes from Playwright configuration, not runtime reads.
  3. Debug what _VIEWPORT_JS returns: run page._stealth_world.evaluate(_VIEWPORT_JS) and confirm {'width','height'} are non-zero; if zero, the environment itself lacks a layout viewport.
  4. Retry once after a short delay for layout races.

Example fix

// before
page.goto('about:blank')
human_scroll_into_view(page, sel, get_box, cfg)

// after
page.goto('https://example.com')
page.wait_for_load_state('domcontentloaded')
human_scroll_into_view(page, sel, get_box, cfg)
Defensive patterns

Strategy: validation

Validate before calling

vp = page.viewport_size or (page._stealth_world.evaluate(_VIEWPORT_JS) if getattr(page, '_stealth_world', None) else None)
if not vp or not vp.get('height'):
    raise RuntimeError('viewport not measurable; wait for layout')

Type guard

def has_viewport(page) -> bool:
    vp = page.viewport_size
    return bool(vp and vp.get('height'))

Try / catch

try:
    human_scroll_into_view(page, sel, get_box, cfg)
except RuntimeError as e:
    if 'Viewport size not available' not in str(e): raise
    page.wait_for_load_state('domcontentloaded')
    human_scroll_into_view(page, sel, get_box, cfg)

Prevention

When it happens

Trigger: world.evaluate(_VIEWPORT_JS) returning None/{} (window.tears down mid-read, unusual embedded webviews), or page.viewport_size returning a dict without height; also passing viewport={} explicitly.

Common situations: Embedded/minimal browser shells (webview, old headless shell) where innerWidth/innerHeight are 0; popup windows opened with window.open before layout; pages in about:blank; races right after navigation where layout is not yet computed.

Related errors


AI-assisted analysis of CloakHQ/CloakBrowser@d6bad5de26 (2026-08-28). Data as JSON: /api/errors/de19cfead6021221. Report an issue: GitHub.