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.

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.

Appendix: 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)