Skip to content

ref(openai-agents): Use first class agent hooks when available - #7029

Draft
alexander-alderman-webb wants to merge 15 commits into
webb/fix-run_single_turn_streamed-patchfrom
webb/use-agent-hooks-2
Draft

ref(openai-agents): Use first class agent hooks when available#7029
alexander-alderman-webb wants to merge 15 commits into
webb/fix-run_single_turn_streamed-patchfrom
webb/use-agent-hooks-2

Conversation

@alexander-alderman-webb

@alexander-alderman-webb alexander-alderman-webb commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Description

Add on_agent_start() and on_agent_end() hooks.

Unlike tool hooks, the Invoke Agent span is stored on the RunHooks instance.

If the hooks are active, start and finish Invoke Agent spans in the hooks in successful executions. If an exception bubbles up, on_agent_end() does not run. Modify monkey-patches so that RunHooks span is finished if the hooks are present.

Make the signatures of invoke_agent_span(), update_invoke_agent_span() and _maybe_start_agent_span() more minimal so that they can be re-used in the on_agent_start() and on_agent_end() hooks.

Issues

Closes #6989

Reminders

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊

95803 passed | ⏭️ 6239 skipped | Total: 102042 | Pass Rate: 93.89% | Execution Time: 350m 33s

📊 Comparison with Base Branch

Metric Change
Total Tests 📈 +189
Passed Tests 📈 +189
Failed Tests
Skipped Tests

All tests are passing successfully.

✅ Patch coverage is 94.12%. Project has 2486 uncovered lines.
✅ Project coverage is 90.02%. Comparing base (base) to head (head).

Files with missing lines (3)
File Patch % Lines
sentry_sdk/integrations/openai_agents/patches/agent_run.py 95.35% ⚠️ 2 Missing and 9 partials
sentry_sdk/integrations/openai_agents/patches/runner.py 92.11% ⚠️ 3 Missing and 3 partials
sentry_sdk/integrations/openai_agents/spans/invoke_agent.py 100.00% ⚠️ 1 partials
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    89.90%    90.02%    +0.12%
==========================================
  Files          193       193         —
  Lines        24825     24919       +94
  Branches      8922      8954       +32
==========================================
+ Hits         22319     22433      +114
- Misses        2506      2486       -20
- Partials      1415      1421        +6

Generated by Codecov Action

@alexander-alderman-webb
alexander-alderman-webb changed the base branch from webb/use-agent-hooks to webb/fix-run_single_turn_streamed-patch August 4, 2026 13:09
@alexander-alderman-webb
alexander-alderman-webb marked this pull request as ready for review August 4, 2026 15:09
@alexander-alderman-webb
alexander-alderman-webb requested a review from a team as a code owner August 4, 2026 15:09

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 4 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 01fd3b1. Configure here.

context: "AgentHookContext[TContext]",
agent: "Agent[TContext]",
) -> "None":
self._sentry_invoke_agent_span = invoke_agent_span(agent, context.turn_input)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Handoffs leak invoke agent spans

High Severity

With use_run_hooks, invoke agent spans live on the hooks instance and are finished in on_agent_end, which only runs on final output. Handoffs never close the prior span: on_agent_start overwrites _sentry_invoke_agent_span, and _execute_handoffs still looks for context_wrapper._sentry_agent_span, which is never set on this path. Each handoff therefore leaves an unfinished invoke agent span.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 01fd3b1. Configure here.

Comment thread sentry_sdk/integrations/openai_agents/patches/runner.py
Comment thread sentry_sdk/integrations/openai_agents/patches/runner.py
Comment thread sentry_sdk/integrations/openai_agents/patches/agent_run.py Outdated
@alexander-alderman-webb
alexander-alderman-webb marked this pull request as draft August 4, 2026 15:33
Comment thread sentry_sdk/integrations/openai_agents/patches/agent_run.py
Comment on lines +242 to +250
if span is not None:
update_invoke_agent_span(
span=span, usage=context_wrapper.usage, agent=agent
)
del run_hooks._sentry_invoke_agent_span
span.__exit__(*exc_info)
else:
span = getattr(context_wrapper, "_sentry_agent_span", None)
if span:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Invoke agent span leaks on exception when user-provided hooks are used

The exception handler retrieves _sentry_invoke_agent_span from run_hooks, but when the user provides a RunHooks instance _patch_run_hooks stores the span on a separate internal _SentryRunHooks object, so the span is never finished and leaks on every exception.

Evidence
  • run_hooks is retrieved from args[2] / kwargs.get("hooks"), which is the user-provided RunHooks instance.
  • _patch_run_hooks in runner.py:129 creates a separate _SentryRunHooks() instance (sentry_hooks) whose on_agent_start stores _sentry_invoke_agent_span on that private object.
  • The patched on_agent_start delegates to sentry_hooks.on_agent_start, but the user's hooks object never receives the _sentry_invoke_agent_span attribute.
  • getattr(run_hooks, "_sentry_invoke_agent_span", None) therefore always resolves to None on patched user hooks.
  • Because the span is never updated or exited, it leaks on every exception path when custom hooks are present.
Also found at 2 additional locations
  • sentry_sdk/integrations/openai_agents/patches/agent_run.py:139
  • sentry_sdk/integrations/openai_agents/patches/runner.py:131-131

Identified by Warden · find-bugs · XD7-X2Q

Comment on lines 103 to 112

def update_invoke_agent_span(
span: "Union[sentry_sdk.tracing.Span, StreamedSpan]",
context: "agents.RunContextWrapper",
usage: "Optional[Usage]",
agent: "agents.Agent",
output: "Any" = None,
) -> None:
# Add aggregated usage data from context_wrapper
if hasattr(context, "usage"):
_set_usage_data(span, context.usage)
if usage is not None:
_set_usage_data(span, usage)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

update_invoke_agent_span lost defensive usage guard, callers crash on missing usage attribute

The update_invoke_agent_span signature changed from taking context (with a hasattr(context, "usage") guard) to taking usage directly. Callers now pass context_wrapper.usage or context.usage without defensive guards. If the context object lacks a usage attribute, callers raise AttributeError inside capture_internal_exceptions(), which suppresses the error but skips span cleanup—leaving spans unfinished.

Evidence
  • update_invoke_agent_span previously checked hasattr(context, "usage") before accessing .usage; the new signature only checks if usage is not None.
  • Callers in patches/agent_run.py and patches/runner.py pass context_wrapper.usage or context.usage directly without getattr or hasattr guards (e.g., agent_run.py:71, agent_run.py:141, runner.py:68).
  • Exception handlers in _run_single_turn (agent_run.py:136-146) and _run_single_turn_streamed (agent_run.py:238-248) wrap these calls inside capture_internal_exceptions(), whose __exit__ returns True, swallowing any AttributeError and blocking the del / span.__exit__ cleanup that follows.
  • The same pattern exists in _SentryRunHooks.on_agent_end (runner.py:66-70), which is executed through a _patch_run_hooks wrapper that also uses capture_internal_exceptions(), so a missing .usage there silently leaks the invoke-agent span on the hooks instance.

Identified by Warden · find-bugs · JK8-BDR

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.

Use RunHooks.on_agent_start and RunHooks.on_agent_end

1 participant