CloakHQ/CloakBrowser · error · StealthWorldUnavailableError

StealthWorldUnavailableError

Error message

StealthWorldUnavailableError

What it means

EnsureActionableAsync requires an IsolatedWorld handle to run its DOM checks in the browser's isolated world; passing a null stealth world (and not force-skipping) is a programming error. The library throws this dedicated error type so callers distinguish 'misuse' from DOM-level failures. It never happens when force: true is set.

Source

Thrown at dotnet/src/CloakBrowser/Human/Actionability.cs:145

    /// <summary>
    /// Wait for the element to pass actionability checks (pre-scroll). Retries
    /// with backoff until <paramref name="timeoutMs"/> elapsed. Throws a specific
    /// <see cref="ActionabilityError"/> subclass on failure. Returns immediately
    /// when <paramref name="force"/> is true.
    /// </summary>
    public static async Task EnsureActionableAsync(
        IPage page,
        string selector,
        IReadOnlySet<string> checks,
        double timeoutMs = 30000,
        bool force = false,
        IsolatedWorld? stealth = null)
    {
        if (force)
            return;
        if (stealth == null)
            throw new StealthWorldUnavailableError();

        double deadline = NowMs() + timeoutMs;
        int attempt = 0;
        Exception? lastError = null;

        while (true)
        {
            double remainingMs = Math.Max(0, deadline - NowMs());
            if (remainingMs <= 0)
            {
                if (lastError != null)
                    throw lastError;
                throw new ActionabilityError(selector, "timeout", "timeout expired before first check");
            }

            try
            {
                var (status, snapshot) = await StealthDom.ActionableAsync(

View on GitHub (pinned to d6bad5de26)

Solutions

  1. Pass the IsolatedWorld obtained from the stealth session setup before calling EnsureActionableAsync.
  2. If you intentionally want to skip the checks, pass force: true instead of null stealth.
  3. Re-acquire the isolated world after navigation/disposal events that invalidate it.
  4. Guard call sites with a null-check that either re-initializes the world or skips humanization.

Example fix

// before
await Actionability.EnsureActionableAsync(page, selector, stealth: null); // throws

// after
if (stealth is null)
    stealth = await page.AcquireIsolatedWorldAsync();
await Actionability.EnsureActionableAsync(page, selector, stealth: stealth);
// or intentionally skip: await Actionability.EnsureActionableAsync(page, selector, force: true);
Defensive patterns

Strategy: type-guard

Validate before calling

if (stealth is null && !force)
    stealth = await page.AcquireIsolatedWorldAsync();
await Actionability.EnsureActionableAsync(page, selector, stealth, force: force);

Type guard

static bool HasStealthWorld(IsolatedWorld? w) => w is { IsClosed: false };

Try / catch

catch (StealthWorldUnavailableError)
{
    stealth = await page.AcquireIsolatedWorldAsync(); // re-acquire and retry once
    await Actionability.EnsureActionableAsync(page, selector, stealth);
}

Prevention

When it happens

Trigger: Calling EnsureActionableAsync/EnsureActionableWorldAsync with stealth: null and force: false — typically when a humanized session was not created, the isolated world was torn down, or a caller forgot to obtain the world handle before humanizing an element.

Common situations: Calling humanize APIs before attaching the stealth isolated world; reusing a session after browser navigation or disposal dropped the world; refactoring that lost the stealth parameter; race where the world is closed mid-flow.

Related errors


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