CloakHQ/CloakBrowser · error · Error

Pro download completed but binary not found at: ${getBinaryP

Error message

Pro download completed but binary not found at: ${getBinaryPath(latest, true)}

What it means

StealthEvaluationError('<scroll-state>') raised by _async_read_scroll_state (scroll_async.py:53) when reading scrollY/scrollHeight/innerHeight through the isolated world either throws or returns a non-dict. It is the async twin of the sync scroll-state read and, like it, has no main-world fallback by design.

Source

Thrown at js/src/download.ts:295

 */
export async function checkForProUpdate(
  licenseKey: string,
  releaseChannel?: string,
): Promise<string | null> {
  const latest = await getProLatestVersion(releaseChannel);
  if (!latest) return null;

  const effective = getEffectiveVersion(true, releaseChannel);
  if (effective && !versionNewer(latest, effective) && proBinaryReady(effective)) {
    // Already on the latest cached Pro build.
    return null;
  }

  if (!proBinaryReady(latest)) {
    console.log(`[cloakbrowser] Downloading Pro Chromium ${latest}...`);
    await downloadProBinary(latest, licenseKey);
    if (!fs.existsSync(getBinaryPath(latest, true))) {
      throw new Error(
        `Pro download completed but binary not found at: ${getBinaryPath(latest, true)}`
      );
    }
  }

  writeProVersionMarker(latest, releaseChannel);
  return latest;
}

// ---------------------------------------------------------------------------
// Welcome message (shown once per install)
// ---------------------------------------------------------------------------

/**
 * Whether the welcome banner should be shown now. Pro: once ever (only when
 * the marker is absent). Free: re-show when the marker is absent or its
 * timestamp is older than WELCOME_FREE_INTERVAL_MS. Unreadable or legacy empty
 * markers count as stale (due).

View on GitHub (pinned to d6bad5de26)

Solutions

  1. Await page.wait_for_load_state(...) before async_human_scroll_into_view so the evaluate does not race navigation.
  2. Guard with page.is_closed() and a not-None _stealth_world check before the call.
  3. Retry the scroll once after asyncio.sleep(0.25) — transient teardown during navigation is the dominant cause.
  4. Check the remote/CDP connection health if failures are persistent.

Example fix

// before
await async_human_scroll_into_view(page, sel, get_box, cfg)

// after
await page.wait_for_load_state('domcontentloaded')
try:
    await async_human_scroll_into_view(page, sel, get_box, cfg)
except StealthEvaluationError:
    await asyncio.sleep(0.25)
    await async_human_scroll_into_view(page, sel, get_box, cfg)
Defensive patterns

Strategy: retry

Validate before calling

if page.is_closed() or getattr(page, "_stealth_world", None) is None:
    raise RuntimeError('page not ready for async stealth scroll')
await page.wait_for_load_state('domcontentloaded')

Type guard

def async_scroll_ready(page) -> bool:
    return (not page.is_closed()) and getattr(page, "_stealth_world", None) is not None

Try / catch

try:
    await async_human_scroll_into_view(page, sel, get_box, cfg)
except StealthEvaluationError:
    await asyncio.sleep(0.25)
    await async_human_scroll_into_view(page, sel, get_box, cfg)

Prevention

When it happens

Trigger: Calling async_human_scroll_into_view while the page is navigating or closing, or when the isolated world was torn down, so world.evaluate(_SCROLL_JS) raises or returns undefined instead of a dict.

Common situations: Async flows that click and immediately scroll (navigation races); awaiting scroll after task cancellation starved the event loop and the CDP session timed out; remote browser connection drops mid-scroll; pages that destroy execution contexts aggressively.

Related errors


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