Skip to content

Add configurable timeout to act tool in agent#1766

Open
tkattkat wants to merge 18 commits intomainfrom
Add-configurable-timeout-to-act-tool-in-agent
Open

Add configurable timeout to act tool in agent#1766
tkattkat wants to merge 18 commits intomainfrom
Add-configurable-timeout-to-act-tool-in-agent

Conversation

@tkattkat
Copy link
Collaborator

@tkattkat tkattkat commented Feb 26, 2026

Why

Agent tool calls can hang indefinitely when the underlying operation stalls (e.g. waiting on a slow LLM inference, an unresponsive page, or a network timeout). When this happens the entire agent loop blocks and the user has no recourse other than killing the process. We need a safety net that bounds how long any single tool call can take and returns control to the LLM so it can adapt.

What changed

We now pass timeout into the tools for ones that involve llm inference within the tool call


Summary by cubic

Adds a configurable per-tool timeout to prevent agent tool calls from hanging. Defaults to 45s, applies only to inference-based tools, and timed-out calls return control with clear JSON errors.

  • New Features

    • agent.execute supports toolTimeout (ms); values ≤ 0 are ignored.
    • Timeouts applied to act, extract, fillForm (observe + act), and ariaTree; forwarded to v3 for true cancellation.
    • ariaTree forwards timeout to extract and returns empty content and pageUrl with success: false on timeout.
    • v3AgentHandler/createAgentTools wire toolTimeout; public types and tests updated.
  • Bug Fixes

    • Timeout errors return friendly messages with standardized success/error statuses; other errors are returned to the agent instead of rethrown.
    • Tool outputs now consistently return JSON error payloads when failing (click, dragAndDrop, type, fillFormVision, screenshot, scroll, wait), improving model parsing.
    • Added e2e tests verifying toolTimeout enforcement and model-visible error serialization for act, extract, fillForm, and ariaTree.

Written for commit 5bfac76. Summary will update on new commits. Review in cubic

@changeset-bot
Copy link

changeset-bot bot commented Feb 26, 2026

🦋 Changeset detected

Latest commit: 5bfac76

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@browserbasehq/stagehand Patch
@browserbasehq/stagehand-evals Patch
@browserbasehq/stagehand-server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 10 files

Confidence score: 2/5

  • The unsanitized error message reflection in packages/core/lib/v3/timeoutConfig.ts can expose arbitrary exception details, which is a high-severity security/privacy risk.
  • Given the severity (8/10) and high confidence, this issue meaningfully raises merge risk despite being a single finding.
  • Pay close attention to packages/core/lib/v3/timeoutConfig.ts - ensure error messages are sanitized for non-TimeoutError exceptions.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/timeoutConfig.ts">

<violation number="1" location="packages/core/lib/v3/timeoutConfig.ts:52">
P1: Custom agent: **Exception and error message sanitization**

The catch-all block reflects unsanitized error messages from arbitrary exceptions. Only `TimeoutError` has a known-safe message; any other error thrown by the underlying tool could leak sensitive information (API keys, CDP URLs, credentials, etc.) through `(error as Error)?.message`. This should either catch `TimeoutError` specifically and re-throw other errors, or sanitize the message before returning it.

For example, catch `TimeoutError` by type and return a generic message for all other errors:
```typescript
catch (error) {
  if (error instanceof TimeoutError) {
    return { success: false, error: error.message };
  }
  throw error;
}
```</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Client as User/SDK Client
    participant Agent as V3AgentHandler
    participant Registry as createAgentTools
    participant Tool as Tool (e.g. act, click)
    participant Utils as timeoutConfig (withTimeout)
    participant V3 as V3 Engine (Browser/LLM)

    Note over Client,V3: Initialization & Tool Wrapping
    Client->>Agent: execute(instruction, { toolTimeout: 30000 })
    Agent->>Registry: NEW: createAgentTools(options including toolTimeout)
    
    loop For each tool (act, click, goto, etc.)
        Registry->>Registry: NEW: wrapToolWithTimeout(tool, timeoutMs)
    end
    Registry-->>Agent: Returns timeout-guarded toolset

    Note over Agent,V3: Execution Loop
    Agent->>Tool: execute(args)
    Tool->>Utils: NEW: withTimeout(originalExecute, timeoutMs)
    
    opt If Tool is v3-method (act, extract, fillForm, ariaTree)
        Tool->>V3: CHANGED: call method with { timeout: toolTimeout }
        Note right of V3: Operation now respects internal deadline
    end

    alt Success Case (within timeout)
        V3-->>Tool: result
        Tool-->>Agent: result
    else Timeout Case (Deadline reached)
        Utils-->>Tool: Throw TimeoutError
        Tool->>Tool: Catch Error
        Tool-->>Agent: NEW: return { success: false, error: "tool timed out..." }
        Note over Agent: Agent can now notify LLM to retry/adapt
    end

    Agent-->>Client: Final Result/Status
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 26, 2026

Greptile Summary

This PR adds a configurable toolTimeout parameter to prevent agent tool calls from hanging indefinitely. The timeout is propagated through the agent handler to inference-based tools (act, extract, fillForm, ariaTree) and forwarded to underlying v3 methods for proper cancellation.

Key changes:

  • Added toolTimeout field to AgentExecuteOptionsBase (optional, values ≤ 0 ignored)
  • Timeout is applied to act, extract, fillForm (both observe and act calls), and ariaTree
  • Tools return friendly timeout error messages instead of throwing, allowing the agent to retry
  • Error handling uses TimeoutError instanceof checks for consistent messaging

Issues found:

  • Documentation claims @default 45000 but implementation doesn't apply any default (becomes undefined when not provided)
  • ariaTree's toModelOutput function doesn't check for errors, so timeout messages won't reach the LLM—model will see empty tree without explanation

Confidence Score: 3/5

  • Safe to merge after fixing the two logic issues with default timeout documentation and ariaTree error propagation
  • The implementation is mostly sound with proper error handling and timeout forwarding. However, two issues need attention: the documented default timeout (45000ms) isn't actually applied, creating a documentation/implementation mismatch, and ariaTree's toModelOutput doesn't propagate timeout errors to the LLM. These are fixable issues that don't break existing functionality but could cause confusion or unexpected behavior.
  • Pay close attention to packages/core/lib/v3/types/public/agent.ts (default timeout mismatch) and packages/core/lib/v3/agent/tools/ariaTree.ts (error propagation issue)

Important Files Changed

Filename Overview
packages/core/lib/v3/agent/tools/act.ts Adds timeout parameter and proper TimeoutError handling with helpful error messages
packages/core/lib/v3/agent/tools/ariaTree.ts Adds timeout support with error handling, but toModelOutput doesn't propagate timeout errors to LLM
packages/core/lib/v3/agent/tools/extract.ts Adds timeout parameter and proper TimeoutError handling with actionable suggestions
packages/core/lib/v3/agent/tools/fillform.ts Adds timeout to both observe and act calls with comprehensive error handling
packages/core/lib/v3/agent/tools/index.ts Adds toolTimeout to options interface with proper validation (ignores values ≤ 0) and wires it to tools
packages/core/lib/v3/types/public/agent.ts Adds toolTimeout field with documentation, but documented default of 45000 doesn't match implementation

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[agent.execute with toolTimeout] --> B[v3AgentHandler.execute]
    B --> C[createTools with toolTimeout]
    C --> D{toolTimeout > 0?}
    D -->|Yes| E[Set toolTimeout value]
    D -->|No| F[Set toolTimeout = undefined]
    E --> G[Pass to Tools]
    F --> G
    G --> H[act tool]
    G --> I[extract tool]
    G --> J[fillForm tool]
    G --> K[ariaTree tool]
    H --> L{Timeout?}
    I --> L
    J --> M{Timeout in observe?}
    K --> N{Timeout in extract?}
    M --> L
    N --> L
    L -->|Yes| O[Return TimeoutError message]
    L -->|No| P[Return success result]
    O --> Q[Agent receives error and retries]
    P --> R[Agent continues]
Loading

Last reviewed commit: 354eb79

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/agent/tools/fillform.ts">

<violation number="1" location="packages/core/lib/v3/agent/tools/fillform.ts:89">
P1: Custom agent: **Exception and error message sanitization**

Unsanitized error message returned from generic catch — `error?.message ?? String(error)` may leak sensitive data (agent variables, API keys, CDP URLs) from underlying `v3.act()` / `v3.observe()` calls. Return a generic sanitized message instead, or filter the error through a sanitization function before surfacing it.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Member

@pirate pirate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have withLlmTimeout which wraps llmClient.createChatCompletion for act/extract/observe and only checks the LLM_MAX_MS env var currently I think.

Separately I think act/extract/observe can also accept timeout args that they enforce themselves

can you try and dedupe these / merge all these timeout mechanisms? right now we have multiple different timeout options enforced at different layers that will likely end up conflicting in some situations and emitting slightly different errors.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/agent/tools/ariaTree.ts">

<violation number="1" location="packages/core/lib/v3/agent/tools/ariaTree.ts:46">
P1: Custom agent: **Exception and error message sanitization**

Unsanitized error message is reflected back as tool content. Errors from `v3.extract()` or `v3.context.awaitActivePage()` may contain CDP URLs, API keys, or other secrets. The `TimeoutError` branch correctly returns a sanitized message, but the generic catch should do the same — return a safe, generic message without reflecting `error.message` verbatim. Consider logging the raw error internally and returning only a sanitized message to the agent.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/agent/tools/ariaTree.ts">

<violation number="1" location="packages/core/lib/v3/agent/tools/ariaTree.ts:48">
P1: Error information is lost before reaching the LLM. Setting `content: ""` while moving the error details to `result.error` means the model only sees an empty `Accessibility Tree:\n` — it has no signal that an error occurred. The `toModelOutput` callback only reads `result.content` and ignores `error`/`success`. Either include the error message in `content` (as it was before), or update `toModelOutput` to surface the `error` field when present.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@tkattkat tkattkat closed this Feb 27, 2026
@pirate pirate reopened this Feb 27, 2026
* reported back to the LLM as a timeout error so it can retry or adjust.
* For tools that call v3 methods (act, extract, fillForm, ariaTree), the
* timeout is also forwarded to the underlying v3 call for true cancellation.
* @default 45000 (45 seconds)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

documentation says default is 45000, but no default is actually applied

The code in index.ts sets toolTimeout to undefined when not provided, rather than 45000:

const toolTimeout = options?.toolTimeout != null && options.toolTimeout > 0 
  ? options.toolTimeout 
  : undefined;

Either apply the default or update the docs to remove @default 45000.

Comment on lines +55 to +58
toModelOutput: (result) => {
if (result.success === false || result.error !== undefined) {
return {
type: "content",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toModelOutput doesn't handle error cases, so timeout errors won't reach the LLM

When a timeout occurs, the execute function returns { content: "", error: "...", success: false }, but toModelOutput only uses result.content, so the model sees an empty tree without the error explanation.

Other tools like click check result.success in toModelOutput:

if (result.success) {
  return { type: "content", value: [{ type: "text", text: `...` }] };
}
return {
  type: "content",
  value: [{ type: "text", text: JSON.stringify({ success: result.success, error: result.error }) }],
};

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 9 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/agent/tools/fillFormVision.ts">

<violation number="1" location="packages/core/lib/v3/agent/tools/fillFormVision.ts:157">
P1: Custom agent: **Exception and error message sanitization**

The error-path `JSON.stringify(result)` serialises the **entire** result object instead of explicitly picking safe fields. `FillFormVisionToolResult` can carry `playwrightArguments` (which contains variable-substituted secrets like passwords and API keys) and `screenshotBase64`. The old code deliberately allowlisted only `{ success, error }` — the new code removes that sanitisation boundary.

Replace with an explicit pick of safe fields to avoid leaking sensitive `variables` or screenshots into the model text output.</violation>
</file>

<file name="packages/core/lib/v3/agent/tools/screenshot.ts">

<violation number="1" location="packages/core/lib/v3/agent/tools/screenshot.ts:29">
P1: Custom agent: **Exception and error message sanitization**

Unsanitized error message reflected to the agent/user. `(error as Error).message` from CDP operations (e.g., `page.screenshot()`, `awaitActivePage()`) may contain sensitive information like CDP connect URLs, session identifiers, or internal endpoint details. Per the error sanitization rule, error messages must be sanitized before being surfaced, and properly typed error classes should be used instead of raw string interpolation.

Consider sanitizing the message (e.g., stripping URLs, tokens, and connection strings) or returning a generic safe message while logging the full error internally.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

} catch (error) {
return {
success: false,
error: `Error taking screenshot: ${(error as Error).message}`,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Custom agent: Exception and error message sanitization

Unsanitized error message reflected to the agent/user. (error as Error).message from CDP operations (e.g., page.screenshot(), awaitActivePage()) may contain sensitive information like CDP connect URLs, session identifiers, or internal endpoint details. Per the error sanitization rule, error messages must be sanitized before being surfaced, and properly typed error classes should be used instead of raw string interpolation.

Consider sanitizing the message (e.g., stripping URLs, tokens, and connection strings) or returning a generic safe message while logging the full error internally.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/agent/tools/screenshot.ts, line 29:

<comment>Unsanitized error message reflected to the agent/user. `(error as Error).message` from CDP operations (e.g., `page.screenshot()`, `awaitActivePage()`) may contain sensitive information like CDP connect URLs, session identifiers, or internal endpoint details. Per the error sanitization rule, error messages must be sanitized before being surfaced, and properly typed error classes should be used instead of raw string interpolation.

Consider sanitizing the message (e.g., stripping URLs, tokens, and connection strings) or returning a generic safe message while logging the full error internally.</comment>

<file context>
@@ -8,22 +8,39 @@ export const screenshotTool = (v3: V3) =>
+      } catch (error) {
+        return {
+          success: false,
+          error: `Error taking screenshot: ${(error as Error).message}`,
+        };
+      }
</file context>
Suggested change
error: `Error taking screenshot: ${(error as Error).message}`,
error: "Error taking screenshot. The operation failed — please retry or check page state.",
Fix with Cubic

@pirate pirate force-pushed the Add-configurable-timeout-to-act-tool-in-agent branch from 354eb79 to 80a4f81 Compare February 27, 2026 20:27
pirate and others added 2 commits February 27, 2026 12:27
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants