From 145e08865235008aa5bd7b6d7978684dd8f3ace0 Mon Sep 17 00:00:00 2001 From: Sean McGuire Date: Wed, 18 Feb 2026 15:02:46 -0800 Subject: [PATCH 1/5] expose cause in UnderstudyCommandException --- .../handlers/handlerUtils/actHandlerUtils.ts | 30 ++++---- .../core/lib/v3/types/public/sdkErrors.ts | 7 ++ .../understudy-command-exception.test.ts | 68 +++++++++++++++++++ 3 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 packages/core/tests/understudy-command-exception.test.ts diff --git a/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts b/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts index a0ad6124e..f9f704178 100644 --- a/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts +++ b/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts @@ -7,14 +7,10 @@ import { resolveLocatorWithHops } from "../../understudy/deepLocator.js"; import type { Page } from "../../understudy/page.js"; import { v3Logger } from "../../logger.js"; import { SessionFileLogger } from "../../flowLogger.js"; -import { StagehandClickError } from "../../types/public/sdkErrors.js"; - -export class UnderstudyCommandException extends Error { - constructor(message: string) { - super(message); - this.name = "UnderstudyCommandException"; - } -} +import { + StagehandClickError, + UnderstudyCommandException, +} from "../../types/public/sdkErrors.js"; export interface UnderstudyMethodHandlerContext { method: string; @@ -127,7 +123,7 @@ export async function performUnderstudyMethod( args: { value: JSON.stringify(args), type: "object" }, }, }); - throw new UnderstudyCommandException(msg); + throw new UnderstudyCommandException(msg, e); } finally { SessionFileLogger.logUnderstudyActionCompleted(); } @@ -173,7 +169,7 @@ export async function selectOption(ctx: UnderstudyMethodHandlerContext) { xpath: { value: xpath, type: "string" }, }, }); - throw new UnderstudyCommandException(e.message); + throw new UnderstudyCommandException(e.message, e); } } @@ -225,7 +221,7 @@ async function scrollByPixelOffset( const { x, y } = await locator.centroid(); await page.scroll(x, y, dx, dy); } catch (e) { - throw new UnderstudyCommandException(e.message); + throw new UnderstudyCommandException(e.message, e); } } @@ -263,7 +259,7 @@ async function fillOrType(ctx: UnderstudyMethodHandlerContext): Promise { xpath: { value: xpath, type: "string" }, }, }); - throw new UnderstudyCommandException(msg); + throw new UnderstudyCommandException(msg, e); } } @@ -282,7 +278,7 @@ async function typeText(ctx: UnderstudyMethodHandlerContext): Promise { xpath: { value: xpath, type: "string" }, }, }); - throw new UnderstudyCommandException(msg); + throw new UnderstudyCommandException(msg, e); } } @@ -312,7 +308,7 @@ async function pressKey(ctx: UnderstudyMethodHandlerContext): Promise { xpath: { value: xpath, type: "string" }, }, }); - throw new UnderstudyCommandException(msg); + throw new UnderstudyCommandException(msg, e); } } @@ -352,7 +348,7 @@ async function doubleClick(ctx: UnderstudyMethodHandlerContext): Promise { xpath: { value: xpath, type: "string" }, }, }); - throw new UnderstudyCommandException(msg); + throw new UnderstudyCommandException(msg, e); } } @@ -429,7 +425,7 @@ async function dragAndDrop(ctx: UnderstudyMethodHandlerContext): Promise { to: { value: toXPath, type: "string" }, }, }); - throw new UnderstudyCommandException(msg); + throw new UnderstudyCommandException(msg, e); } } @@ -518,7 +514,7 @@ export async function hover(ctx: UnderstudyMethodHandlerContext) { xpath: { value: xpath, type: "string" }, }, }); - throw new UnderstudyCommandException(e.message); + throw new UnderstudyCommandException(e.message, e); } } diff --git a/packages/core/lib/v3/types/public/sdkErrors.ts b/packages/core/lib/v3/types/public/sdkErrors.ts index 88389afc7..0f6beb2be 100644 --- a/packages/core/lib/v3/types/public/sdkErrors.ts +++ b/packages/core/lib/v3/types/public/sdkErrors.ts @@ -399,3 +399,10 @@ export class StagehandSnapshotError extends StagehandError { super(`error taking snapshot${suffix}`, cause); } } + +export class UnderstudyCommandException extends StagehandError { + constructor(message: string, cause?: unknown) { + super(message, cause); + this.name = "UnderstudyCommandException"; + } +} diff --git a/packages/core/tests/understudy-command-exception.test.ts b/packages/core/tests/understudy-command-exception.test.ts new file mode 100644 index 000000000..1304f84be --- /dev/null +++ b/packages/core/tests/understudy-command-exception.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from "vitest"; +import { + UnderstudyCommandException, + StagehandError, +} from "../lib/v3/types/public/sdkErrors.js"; + +describe("UnderstudyCommandException", () => { + it("extends StagehandError", () => { + const err = new UnderstudyCommandException("test"); + expect(err).toBeInstanceOf(StagehandError); + expect(err).toBeInstanceOf(Error); + }); + + it("has the correct name", () => { + const err = new UnderstudyCommandException("test"); + expect(err.name).toBe("UnderstudyCommandException"); + }); + + it("preserves the message", () => { + const err = new UnderstudyCommandException("something broke"); + expect(err.message).toBe("something broke"); + }); + + it("stores the original error as cause when provided", () => { + const original = new Error("root cause"); + const err = new UnderstudyCommandException("wrapper message", original); + + expect(err.cause).toBe(original); + expect((err.cause as Error).message).toBe("root cause"); + expect((err.cause as Error).stack).toBeDefined(); + }); + + it("stores non-Error cause values", () => { + const err = new UnderstudyCommandException("failed", "string cause"); + expect(err.cause).toBe("string cause"); + }); + + it("has undefined cause when none is provided", () => { + const err = new UnderstudyCommandException("no cause"); + expect(err.cause).toBeUndefined(); + }); + + it("generates its own stack trace", () => { + const err = new UnderstudyCommandException("test"); + expect(err.stack).toBeDefined(); + expect(err.stack).toContain("UnderstudyCommandException"); + }); + + it("preserves the original stack via cause for debugging", () => { + function deepFunction() { + throw new Error("deep error"); + } + + let original: Error; + try { + deepFunction(); + } catch (e) { + original = e as Error; + } + + const wrapped = new UnderstudyCommandException(original!.message, original); + + // The wrapper has its own stack + expect(wrapped.stack).toBeDefined(); + // The original stack is accessible via cause + expect((wrapped.cause as Error).stack).toContain("deepFunction"); + }); +}); From fbe2e7686e3037b3855b364884d95b7a15e0a671 Mon Sep 17 00:00:00 2001 From: Sean McGuire Date: Wed, 18 Feb 2026 15:04:35 -0800 Subject: [PATCH 2/5] changeset --- .changeset/tender-zoos-think.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tender-zoos-think.md diff --git a/.changeset/tender-zoos-think.md b/.changeset/tender-zoos-think.md new file mode 100644 index 000000000..f6f7507c9 --- /dev/null +++ b/.changeset/tender-zoos-think.md @@ -0,0 +1,5 @@ +--- +"@browserbasehq/stagehand": patch +--- + +include error cause in UnderstudyCommandException From 8e78a14286451659394fe54cf93043becf2248a7 Mon Sep 17 00:00:00 2001 From: Sean McGuire Date: Wed, 18 Feb 2026 15:19:55 -0800 Subject: [PATCH 3/5] check type before accessing e.message and e.stack --- .../handlers/handlerUtils/actHandlerUtils.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts b/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts index f9f704178..13cafa791 100644 --- a/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts +++ b/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts @@ -159,17 +159,19 @@ export async function selectOption(ctx: UnderstudyMethodHandlerContext) { const text = args[0]?.toString() || ""; await locator.selectOption(text); } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + const stack = e instanceof Error ? e.stack : undefined; v3Logger({ category: "action", message: "error selecting option", level: 0, auxiliary: { - error: { value: e.message, type: "string" }, - trace: { value: e.stack, type: "string" }, + error: { value: msg, type: "string" }, + trace: { value: stack ?? "", type: "string" }, xpath: { value: xpath, type: "string" }, }, }); - throw new UnderstudyCommandException(e.message, e); + throw new UnderstudyCommandException(msg, e); } } @@ -221,7 +223,8 @@ async function scrollByPixelOffset( const { x, y } = await locator.centroid(); await page.scroll(x, y, dx, dy); } catch (e) { - throw new UnderstudyCommandException(e.message, e); + const msg = e instanceof Error ? e.message : String(e); + throw new UnderstudyCommandException(msg, e); } } @@ -504,17 +507,19 @@ export async function hover(ctx: UnderstudyMethodHandlerContext) { try { await locator.hover(); } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + const stack = e instanceof Error ? e.stack : undefined; v3Logger({ category: "action", message: "error attempting to hover", level: 0, auxiliary: { - error: { value: e.message, type: "string" }, - trace: { value: e.stack, type: "string" }, + error: { value: msg, type: "string" }, + trace: { value: stack ?? "", type: "string" }, xpath: { value: xpath, type: "string" }, }, }); - throw new UnderstudyCommandException(e.message, e); + throw new UnderstudyCommandException(msg, e); } } From 5b39abea6d472e2c4b4eaa2207e1b3f76c9b8f30 Mon Sep 17 00:00:00 2001 From: Sean McGuire Date: Wed, 18 Feb 2026 15:27:39 -0800 Subject: [PATCH 4/5] update export surface test --- packages/core/tests/public-api/public-error-types.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/tests/public-api/public-error-types.test.ts b/packages/core/tests/public-api/public-error-types.test.ts index 8f2dc170e..354fe5265 100644 --- a/packages/core/tests/public-api/public-error-types.test.ts +++ b/packages/core/tests/public-api/public-error-types.test.ts @@ -60,6 +60,7 @@ export const publicErrorTypes = { ActTimeoutError: Stagehand.ActTimeoutError, ObserveTimeoutError: Stagehand.ObserveTimeoutError, ExtractTimeoutError: Stagehand.ExtractTimeoutError, + UnderstudyCommandException: Stagehand.UnderstudyCommandException, } as const; const errorTypes = Object.keys(publicErrorTypes) as Array< From e2047d6456c6f07dcbbf5816a11da5f0c687ab6c Mon Sep 17 00:00:00 2001 From: Sean McGuire Date: Wed, 18 Feb 2026 15:32:52 -0800 Subject: [PATCH 5/5] dont double wrap errors --- packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts b/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts index 13cafa791..d99801f8c 100644 --- a/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts +++ b/packages/core/lib/v3/handlers/handlerUtils/actHandlerUtils.ts @@ -123,6 +123,9 @@ export async function performUnderstudyMethod( args: { value: JSON.stringify(args), type: "object" }, }, }); + if (e instanceof UnderstudyCommandException) { + throw e; + } throw new UnderstudyCommandException(msg, e); } finally { SessionFileLogger.logUnderstudyActionCompleted();