Skip to content

agent_tool_end event not emitted when function tool throws error #753

@aasullivan

Description

@aasullivan

Please read this first

  • Have you read the docs? Agents SDK docs
  • Have you searched for related issues? No existing issues found for this event lifecycle bug.

Describe the bug

Function tools don't emit agent_tool_end events when an error is thrown during tool execution, breaking the expected event lifecycle where every agent_tool_start should be paired with an agent_tool_end. This creates inconsistent behavior compared to shell, computer, and apply patch tools, which all emit agent_tool_end even on errors.

This breaks event-driven systems that depend on event pairing, including:

  • Event sourcing systems tracking tool executions
  • Monitoring/observability systems measuring tool completion rates
  • Debugging tools tracking execution time
  • Resource management systems that acquire/release in event handlers

Debug information

  • Agents SDK version: v0.3.4 (affects all versions)
  • Runtime environment: Node.js 22.17.0 (affects all environments)

Repro steps

import { Agent, tool, Runner } from '@openai/agents-core';
import { z } from 'zod';

// Create a tool that throws an error
const failingTool = tool({
  name: 'failing_tool',
  description: 'A tool that throws an error',
  parameters: z.object({}),
  errorFunction: null, // Disable default error handler to expose the bug
  execute: async () => {
    throw new Error('Tool execution failed');
  },
});

const agent = new Agent({
  name: 'TestAgent',
  tools: [failingTool],
});

const runner = new Runner();

// Track events
const events: string[] = [];
runner.on('agent_tool_start', () => events.push('start'));
runner.on('agent_tool_end', () => events.push('end'));

// Execute the tool
try {
  await runner.run(agent, 'Use the failing_tool');
} catch (error) {
  // Error is thrown as expected
}

console.log(events); // Output: ['start'] ❌ Missing 'end'
// Expected: ['start', 'end'] ✅

Observed behavior: Only agent_tool_start is emitted. The agent_tool_end event is never fired.

Code location: packages/agents-core/src/runImplementation.ts, lines 1296-1361 in runSingleTool()

try {
  runner.emit('agent_tool_start', ...);
  const toolOutput = await toolRun.tool.invoke(...);

  // Only reached on success ❌
  runner.emit('agent_tool_end', ...);

} catch (error) {
  span.setError({ ... });
  throw error; // Re-throws without emitting agent_tool_end
}

Expected behavior

Function tools should emit agent_tool_end even when errors occur, matching the behavior of other tool types:

Shell tools (lines 1544-1578): ✅ Catch errors, create error outputs, always emit agent_tool_end
Computer tools (lines 1761-1774): ✅ Catch errors, set empty output, always emit agent_tool_end
Apply patch tools (lines 1700-1724): ✅ Catch errors, set failed status, always emit agent_tool_end
Function tools (lines 1352-1360): ❌ Catch errors, never emit agent_tool_end, re-throw immediately

The agent_tool_end event should be emitted in the catch block before re-throwing:

try {
  runner.emit('agent_tool_start', ...);
  const toolOutput = await toolRun.tool.invoke(...);
  runner.emit('agent_tool_end', ..., stringResult, { toolCall });

} catch (error) {
  span.setError({ ... });

  // ✅ Emit agent_tool_end before re-throwing
  const errorResult = String(error);
  runner.emit('agent_tool_end', ..., errorResult, { toolCall });

  throw error; // Still re-throw to maintain backward compatibility
}

Benefits:

  • Maintains consistent event lifecycle across all tool types
  • Event listeners always receive both start and end events
  • Fully backward compatible (error is still re-thrown)
  • Enables proper monitoring, metrics, and event sourcing

Note: Most users won't see this directly because the SDK has a default error handler that converts tool errors to error strings. However, the inconsistent event emission still occurs and affects any system listening to these events, or users who disable the default error handler for custom error handling.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions