Skip to content

Don't record pydantic-ai CallDeferred exception or set level to error#1743

Open
Br1an67 wants to merge 4 commits intopydantic:mainfrom
Br1an67:fix/ignore-call-deferred-exception
Open

Don't record pydantic-ai CallDeferred exception or set level to error#1743
Br1an67 wants to merge 4 commits intopydantic:mainfrom
Br1an67:fix/ignore-call-deferred-exception

Conversation

@Br1an67
Copy link

@Br1an67 Br1an67 commented Feb 28, 2026

Summary

Skip recording pydantic-ai's CallDeferred and ApprovalRequired exceptions as errors in logfire spans. These are control flow exceptions used for deferred tool calls and human-in-the-loop approval, not actual errors.

Changes

  • logfire/_internal/tracer.py: Added _is_pydantic_ai_control_flow_exception() helper with lazy import (to avoid circular imports) that checks if an exception is CallDeferred or ApprovalRequired. Added an early return in record_exception() to skip recording these exceptions entirely — no error status, no error level, no exception event.
  • tests/otel_integrations/test_pydantic_ai.py: Added parametrized test for both CallDeferred and ApprovalRequired verifying the span has no exception events and no error level.

Closes #1515

Skip recording pydantic-ai's CallDeferred and ApprovalRequired exceptions
as errors in logfire spans. These are control flow exceptions used for
deferred tool calls and human-in-the-loop approval, not actual errors.

Uses lazy import to avoid circular imports between logfire and pydantic-ai.

Closes pydantic#1515
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment on lines +424 to +428
# pydantic-ai uses CallDeferred and ApprovalRequired as control flow exceptions
# for deferred tool calls and human-in-the-loop approval.
# They should not be recorded as errors.
if _is_pydantic_ai_control_flow_exception(exception):
return
Copy link
Contributor

Choose a reason for hiding this comment

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

🚩 Incomplete coverage when exception escapes through OTel SDK's use_span (via start_as_current_span)

The fix correctly suppresses error recording for logfire.span(...) context managers (which use LogfireSpan.__exit___LogfireWrappedSpan.record_exception → module-level record_exception). However, when external OTel instrumentation creates spans via _ProxyTracer.start_as_current_span (logfire/_internal/tracer.py:310), the OTel SDK's use_span context manager catches exceptions and calls both span.record_exception(error) and span.set_status(ERROR, ...) independently. The record_exception call is correctly intercepted and skipped, but set_status(ERROR) is called directly by use_span and is NOT gated by the new control flow exception check.

In practice, this likely doesn't matter for the stated use case: pydantic-ai catches CallDeferred/ApprovalRequired internally within its own instrumented spans, so these exceptions shouldn't escape through start_as_current_span-created spans. But if that assumption changes, spans could still get ERROR status without any recorded exception event.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Br1an67 and others added 2 commits March 1, 2026 01:27
…tatus

- Add fallback CallDeferred/ApprovalRequired definitions in test_pydantic_ai.py
  so @pytest.mark.parametrize resolves at module level on Python < 3.10
  where pydantic-ai may not be available
- Add control flow exception check to set_exception_status() for defense
  in depth against ERROR status being set on control flow exceptions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ntic versions

On pydantic 2.4, importing pydantic-ai exceptions may raise errors
other than ImportError (e.g. AttributeError from version incompatibility).
Use except Exception to gracefully handle all failure modes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codecov
Copy link

codecov bot commented Feb 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The check in record_exception() already returns early for pydantic-ai
control flow exceptions before set_exception_status() is called.
The duplicate check was dead code causing coverage to drop below 100%.

Co-authored-by: Copilot <223556219+Copilot@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.

Don't record pydantic-ai CallDeferred exception or set level to error

1 participant