mastra-ai/mastra · error

browser_close requires agent.threadId when browser scope is

Error message

browser_close requires agent.threadId when browser scope is not shared

What it means

browser_close in non-shared (thread) scope closes only the calling thread's browser session, which requires knowing which thread to close. The tool reads agent.threadId; if the agent has no threadId and the browser scope is not 'shared', there is no session identity to close, so the tool throws. It prevents accidentally closing a shared browser or silently doing nothing.

Source

Thrown at browser/agent-browser/src/tools/close.ts:19

/**
 * browser_close - Close the browser
 */
import { createTool } from '@mastra/core/tools';
import type { AgentBrowser } from '../agent-browser';
import { closeInputSchema } from '../schemas';
import { BROWSER_TOOLS } from './constants';
export function createCloseTool(browser: AgentBrowser) {
  return createTool({
    id: BROWSER_TOOLS.CLOSE,
    description: 'Close the browser. Only use when done with all browsing.',
    inputSchema: closeInputSchema,
    execute: async (_input, { agent }) => {
      // For thread scope, close only the thread's session
      const threadId = agent?.threadId;
      browser.setCurrentThread(threadId);
      if (browser.getScope() !== 'shared') {
        if (!threadId) {
          throw new Error('browser_close requires agent.threadId when browser scope is not shared');
        }
        browser.markBrowserCloseReason('agent', threadId);
        await browser.closeThreadSession(threadId);
        return { success: true, hint: "Thread's browser session closed. A new session will be created on next use." };
      }
      // For shared scope, close the entire browser
      browser.markBrowserCloseReason('agent');
      await browser.close();
      return { success: true, hint: 'Browser closed. It will be re-launched automatically on next use.' };
    },
  });
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Configure the agent run so a threadId is available (enable memory/threading or pass threadId at run start).
  2. Switch the browser tooling to shared scope so no per-thread identity is required.
  3. Skip browser_close when threadId is absent and rely on TTL/idle session cleanup.
  4. Catch the error and return structured guidance to the model to provide thread context.

Example fix

// before
await closeTool.execute({}, { agent: {} }); // throws: no threadId
// after
await closeTool.execute({}, { agent: { threadId: 'thread-123' } });
// or use shared scope
const tools = createAgentBrowserTools({ scope: 'shared' });
Defensive patterns

Strategy: validation

Validate before calling

if (!agent?.threadId && browser.getScope() !== 'shared') {
  throw new Error('Cannot close browser: agent.threadId is required for non-shared scope');
}
await closeTool.execute({}, { agent });

Type guard

function hasThreadContext(agent: unknown): agent is { threadId: string } {
  return typeof agent === 'object' && agent !== null &&
    'threadId' in agent && typeof (agent as { threadId?: unknown }).threadId === 'string' &&
    (agent as { threadId: string }).threadId.length > 0;
}

Try / catch

try {
  await closeTool.execute({}, { agent });
} catch (err) {
  if (err instanceof Error && err.message.includes('requires agent.threadId')) {
    return { success: false, reason: 'missing-thread-context' };
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling the browser_close tool from an agent run where agent.threadId is undefined (agent invoked without thread/memory context) while browser scope is any value other than 'shared'.

Common situations: Running an agent without memory/threading configured; custom runners that don't populate agent.threadId; tests or scripts invoking the tool's execute directly without an agent context.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/42fcd0c97a62e95e. Report an issue: GitHub.