Skip to main content

Overview

Robust error handling is essential for production Stagehand applications. This guide covers error types, recovery strategies, and best practices.

Error Types

Stagehand provides specific error classes for different failure modes.

Timeout Errors

import { 
  ActTimeoutError, 
  ExtractTimeoutError, 
  ObserveTimeoutError 
} from "@browserbasehq/stagehand";

try {
  await stagehand.act("click submit button", { timeout: 5000 });
} catch (error) {
  if (error instanceof ActTimeoutError) {
    console.log("Action timed out after 5 seconds");
    // Retry with longer timeout or different strategy
  }
}
Timeout errors:
  • ActTimeoutError - act() operation timed out
  • ExtractTimeoutError - extract() operation timed out
  • ObserveTimeoutError - observe() operation timed out
  • TimeoutError - Base timeout error class

Element Not Found Errors

import { StagehandElementNotFoundError } from "@browserbasehq/stagehand";

try {
  await stagehand.act("click the non-existent button");
} catch (error) {
  if (error instanceof StagehandElementNotFoundError) {
    console.log("Element not found:", error.message);
    // Element doesn't exist on page
  }
}

Initialization Errors

import { 
  StagehandNotInitializedError,
  StagehandInitError 
} from "@browserbasehq/stagehand";

try {
  const stagehand = new Stagehand({ env: "LOCAL" });
  // ❌ Forgot to call init()
  await stagehand.act("click button");
} catch (error) {
  if (error instanceof StagehandNotInitializedError) {
    console.log("Stagehand not initialized. Call await stagehand.init() first.");
  }
}

Configuration Errors

import { 
  MissingEnvironmentVariableError,
  UnsupportedModelError,
  MissingLLMConfigurationError
} from "@browserbasehq/stagehand";

try {
  const stagehand = new Stagehand({
    env: "BROWSERBASE",
    // Missing apiKey and projectId
  });
  await stagehand.init();
} catch (error) {
  if (error instanceof MissingEnvironmentVariableError) {
    console.log("Missing required environment variable:", error.message);
  }
}

LLM Response Errors

import { LLMResponseError } from "@browserbasehq/stagehand";

try {
  await stagehand.extract("get product data");
} catch (error) {
  if (error instanceof LLMResponseError) {
    console.log("LLM returned invalid response:", error.message);
    // Retry or fall back to different approach
  }
}

Validation Errors

import { ZodSchemaValidationError } from "@browserbasehq/stagehand";
import { z } from "zod";

const schema = z.object({
  price: z.number(),
  title: z.string(),
});

try {
  const result = await stagehand.extract(
    "extract product info",
    { schema }
  );
} catch (error) {
  if (error instanceof ZodSchemaValidationError) {
    console.log("Extraction didn't match schema:", error.issues);
    console.log("Received:", error.received);
  }
}

Error Handling Patterns

Basic Try-Catch

try {
  await stagehand.act("click submit");
} catch (error) {
  console.error("Action failed:", error);
  throw error; // Re-throw or handle
}

Retry with Exponential Backoff

async function actWithRetry(
  stagehand: Stagehand,
  instruction: string,
  maxRetries = 3
) {
  let lastError;
  
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await stagehand.act(instruction);
    } catch (error) {
      lastError = error;
      const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s
      console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  throw lastError;
}

// Usage
try {
  await actWithRetry(stagehand, "click the submit button");
} catch (error) {
  console.error("Failed after retries:", error);
}

Graceful Degradation

let productData;

try {
  // Try AI-powered extraction
  const result = await stagehand.extract("extract product details");
  productData = result.extraction;
} catch (error) {
  console.warn("AI extraction failed, falling back to selectors");
  
  // Fall back to direct selectors
  const page = stagehand.context.pages()[0];
  productData = {
    title: await page.textContent(".product-title"),
    price: await page.textContent(".product-price"),
  };
}

Timeout Strategy

const SHORT_TIMEOUT = 5_000;
const MEDIUM_TIMEOUT = 15_000;
const LONG_TIMEOUT = 30_000;

try {
  // Try with short timeout first
  await stagehand.act("click button", { timeout: SHORT_TIMEOUT });
} catch (error) {
  if (error instanceof ActTimeoutError) {
    console.log("Retrying with longer timeout...");
    // Retry with longer timeout
    await stagehand.act("click button", { timeout: MEDIUM_TIMEOUT });
  } else {
    throw error;
  }
}

Handling Agent Errors

Agent Abort

import { AgentAbortError } from "@browserbasehq/stagehand";

const agent = stagehand.agent();

try {
  const result = await agent.execute({
    instruction: "complete the checkout process",
    maxSteps: 20,
  });
} catch (error) {
  if (error instanceof AgentAbortError) {
    console.log("Agent aborted:", error.reason);
    // Agent couldn't complete the task
  }
}

Agent Max Steps Reached

const agent = stagehand.agent();

const result = await agent.execute({
  instruction: "find and click the submit button",
  maxSteps: 5,
});

if (!result.success) {
  console.log("Agent couldn't complete task in 5 steps");
  console.log("Steps taken:", result.actions?.length);
  
  // Retry with more steps
  const retryResult = await agent.execute({
    instruction: "find and click the submit button",
    maxSteps: 10,
  });
}

Connection Errors

CDP Connection Closed

import { CdpConnectionClosedError } from "@browserbasehq/stagehand";

try {
  await stagehand.act("click button");
} catch (error) {
  if (error instanceof CdpConnectionClosedError) {
    console.log("Browser connection closed:", error.message);
    // Browser crashed or session ended
    // Need to create new Stagehand instance
  }
}

Browserbase Connection

import { 
  ConnectionTimeoutError,
  BrowserbaseSessionNotFoundError 
} from "@browserbasehq/stagehand";

try {
  const stagehand = new Stagehand({
    env: "BROWSERBASE",
    apiKey: process.env.BROWSERBASE_API_KEY,
    projectId: process.env.BROWSERBASE_PROJECT_ID,
  });
  await stagehand.init();
} catch (error) {
  if (error instanceof ConnectionTimeoutError) {
    console.log("Connection timed out:", error.message);
  } else if (error instanceof BrowserbaseSessionNotFoundError) {
    console.log("Session not found");
  }
}

Cleanup and Resource Management

Always Close

const stagehand = new Stagehand({ env: "LOCAL" });

try {
  await stagehand.init();
  await stagehand.act("perform actions");
} catch (error) {
  console.error("Error during execution:", error);
  throw error;
} finally {
  // Always clean up resources
  await stagehand.close();
}

Detect Closed State

import { StagehandClosedError } from "@browserbasehq/stagehand";

try {
  await stagehand.act("click button");
} catch (error) {
  if (error instanceof StagehandClosedError) {
    console.log("Stagehand session was closed");
    // Cannot use this instance anymore
  }
}

Logging Errors

Custom Logger

const stagehand = new Stagehand({
  env: "LOCAL",
  verbose: 2,
  logger: (line) => {
    if (line.level === 0) {
      // Error level
      console.error(`[${line.category}] ${line.message}`, line.auxiliary);
    }
  },
});

Persist Error Logs

import fs from "fs";

const stagehand = new Stagehand({
  env: "LOCAL",
  verbose: 2,
  logInferenceToFile: true, // Saves detailed logs to file
});

try {
  await stagehand.init();
  await stagehand.act("click button");
} catch (error) {
  // Error logs automatically saved to file
  console.error("Check logs directory for details");
  throw error;
}

Production Error Handling

Comprehensive Error Handler

import {
  ActTimeoutError,
  StagehandElementNotFoundError,
  LLMResponseError,
  StagehandError,
} from "@browserbasehq/stagehand";

async function performAction(
  stagehand: Stagehand,
  instruction: string
) {
  try {
    return await stagehand.act(instruction, { timeout: 15_000 });
  } catch (error) {
    // Handle specific errors
    if (error instanceof ActTimeoutError) {
      console.error("Action timed out");
      // Retry or notify user
      throw new Error("Operation timed out. Please try again.");
    } else if (error instanceof StagehandElementNotFoundError) {
      console.error("Element not found:", error.message);
      // Page structure may have changed
      throw new Error("Could not find the required element.");
    } else if (error instanceof LLMResponseError) {
      console.error("LLM error:", error.message);
      // Model issue, retry with different model
      throw new Error("AI service error. Please try again.");
    } else if (error instanceof StagehandError) {
      console.error("Stagehand error:", error.message);
      throw error;
    } else {
      // Unknown error
      console.error("Unexpected error:", error);
      throw new Error("An unexpected error occurred.");
    }
  }
}

Error Monitoring

import * as Sentry from "@sentry/node";

try {
  await stagehand.act("click button");
} catch (error) {
  // Report to error tracking service
  Sentry.captureException(error, {
    tags: {
      operation: "act",
      instruction: "click button",
    },
    extra: {
      page_url: stagehand.context.pages()[0]?.url(),
    },
  });
  
  throw error;
}

Testing Error Scenarios

import { test, expect } from "@playwright/test";

test("handles timeout gracefully", async () => {
  const stagehand = new Stagehand({ env: "LOCAL" });
  await stagehand.init();
  
  try {
    // This should timeout
    await stagehand.act("click non-existent button", { timeout: 1000 });
    
    // Should not reach here
    expect(true).toBe(false);
  } catch (error) {
    expect(error).toBeInstanceOf(ActTimeoutError);
  } finally {
    await stagehand.close();
  }
});

test("retries on failure", async () => {
  const stagehand = new Stagehand({ env: "LOCAL" });
  await stagehand.init();
  
  let attempts = 0;
  const maxAttempts = 3;
  let result;
  
  for (let i = 0; i < maxAttempts; i++) {
    try {
      attempts++;
      result = await stagehand.act("click button");
      break; // Success
    } catch (error) {
      if (i === maxAttempts - 1) throw error;
      await new Promise(r => setTimeout(r, 1000));
    }
  }
  
  expect(attempts).toBeLessThanOrEqual(maxAttempts);
  expect(result).toBeDefined();
  
  await stagehand.close();
});

Error Prevention

Validate Before Acting

const page = stagehand.context.pages()[0];

// Check if element exists before acting
const buttonExists = await page.locator("button[type='submit']").count() > 0;

if (buttonExists) {
  await stagehand.act("click the submit button");
} else {
  console.log("Submit button not found on page");
  // Handle missing element
}

Wait for Page State

const page = stagehand.context.pages()[0];
await page.goto("https://example.com");

// Wait for content to load
await page.waitForLoadState("networkidle");

// Now safe to act
await stagehand.act("click the first product");

Use Appropriate Timeouts

// Short timeout for simple actions
await stagehand.act("click button", { timeout: 5_000 });

// Longer timeout for complex operations
await stagehand.extract("extract all product data", { timeout: 30_000 });

// Very long timeout for agent workflows
const agent = stagehand.agent();
await agent.execute({
  instruction: "complete checkout",
  maxSteps: 20,
  // Individual steps have their own timeouts
});

Error Reference

Common errors from sdkErrors.ts:1-438:
Error ClassWhen It OccursRecovery Strategy
ActTimeoutErroract() times outIncrease timeout, retry
ExtractTimeoutErrorextract() times outIncrease timeout, simplify extraction
ObserveTimeoutErrorobserve() times outIncrease timeout, be more specific
StagehandElementNotFoundErrorElement doesn’t existCheck page structure, wait for load
StagehandNotInitializedErrorForgot to call init()Call await stagehand.init()
MissingLLMConfigurationErrorNo LLM API keySet API key or provide LLM client
ZodSchemaValidationErrorExtraction doesn’t match schemaAdjust schema or prompt
AgentAbortErrorAgent can’t complete taskAdjust instruction or increase steps
CdpConnectionClosedErrorBrowser connection lostCreate new Stagehand instance
StagehandClosedErrorUsed after close()Don’t use after closing

Build docs developers (and LLMs) love