chenglou/pretext · error · Error

Firefox BiDi returned no browsing context

Error message

Firefox BiDi returned no browsing context

What it means

Thrown during Firefox session initialization when browsingContext.getTree succeeded (no error field) but the returned contexts array is empty (contexts[0]?.context is undefined). Firefox launched with about:blank but reported zero usable browsing contexts, so there is nowhere to navigate. This is distinct from error 17 (which fires when getTree itself errors); here the call succeeded with empty content.

Source

Thrown at scripts/browser-automation.ts:392

  try {
    await waitForPort(bidiPort)
    bidi = await connectFirefoxBidi(bidiPort)

    const session = await bidi.send('session.new', { capabilities: { alwaysMatch: {} } })
    if (session.error !== undefined) {
      throw new Error(session.message ?? session.error)
    }

    const tree = await bidi.send('browsingContext.getTree', {})
    if (tree.error !== undefined) {
      throw new Error(tree.message ?? tree.error)
    }

    const contexts = (tree.result as { contexts: Array<{ context: string }> }).contexts
    const context = contexts[0]?.context
    if (context === undefined) {
      throw new Error('Firefox BiDi returned no browsing context')
    }

    return {
      bidi,
      context,
      firefoxProcess,
      profileDir,
    }
  } catch (error) {
    bidi?.close()
    try {
      firefoxProcess.kill('SIGTERM')
    } catch {
      // Best effort cleanup.
    }
    rmSync(profileDir, { recursive: true, force: true })
    throw error
  }

View on GitHub (pinned to ac49b09b7d)

Solutions

  1. Re-run — if it is a startup race, a second attempt may catch the context after it appears.
  2. Run Firefox headless manually with the same flags and confirm a tab opens (check about:debugging or ps for content processes).
  3. Ensure no other Firefox instance is using the same profile dir (the script uses a fresh mkdtemp profile, but verify).
  4. On containers/sandboxes, allow content-process forking and sufficient shared memory.
Defensive patterns

Strategy: retry

Type guard

function hasBrowsingContext(tree: { result?: { contexts?: Array<{ context: string }> } }): boolean {
  return (tree.result?.contexts?.length ?? 0) > 0
}

Try / catch

// Retry getTree once after a short delay in case the context appears late.
let contexts = (tree.result as { contexts: Array<{ context: string }> }).contexts
if (contexts.length === 0) {
  await sleep(500)
  const retry = await bidi.send('browsingContext.getTree', {})
  contexts = (retry.result as { contexts: Array<{ context: string }> }).contexts
}
if (contexts[0]?.context === undefined) throw new Error('Firefox BiDi returned no browsing context')

Prevention

When it happens

Trigger: Firefox opened in --headless with --new-instance but produced no browsing context — e.g. a content process failed to start, a sandbox policy blocked tab creation, or a profile lock prevented a real session. The BiDi handshake worked but the browser has no tab.

Common situations: Headless Firefox on a restricted/sandboxed environment where content processes cannot spawn; a profile that Firefox considers locked by another instance; a minimal/containerized environment lacking the resources to create a tab; Firefox startup race where the context appears slightly after the query.

Related errors


AI-assisted analysis of chenglou/pretext@ac49b09b7d (2026-08-12). Data as JSON: /api/errors/15d997647b100ef7. Report an issue: GitHub.