Skip to content

Restore tool spans for failed argument validation - #6601

Merged
adtyavrdhn merged 33 commits into
mainfrom
codex/issue-6555-wrap-tool-execute
Aug 10, 2026
Merged

adtyavrdhn merged 33 commits into
mainfrom
codex/issue-6555-wrap-tool-execute

Conversation

@adtyavrdhn

@adtyavrdhn adtyavrdhn commented Jul 20, 2026 •

Copy link
Copy Markdown
Member

Closes #6555

Summary

  • restore an error execute_tool span when argument validation ultimately rejects a tool call
  • let recovery capabilities run first, avoiding false error spans
  • share retry-prompt construction so telemetry matches the prompt sent to the model
  • redact rejected values when include_content=False

Scope

The public validation and execution lifecycle is unchanged. The shared retry helper is internal.

Verification

Validation-error, recovery, retry, and redaction coverage passes with focused Logfire tests, lint, formatting, and type checking.

Checklist

  • Any AI generated code has been reviewed line-by-line by the human PR author, who stands by it.
  • No breaking changes in accordance with the version policy.
  • PR title is fit for the release changelog.

@github-actions github-actions Bot added size: M Medium PR (101-500 weighted lines) bug Report that something isn't working, or PR implementing a fix labels Jul 20, 2026

@github-actions github-actions Bot left a comment

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.

Review summary

Reviewed the diff for restoring the v1.94.0 behavior of recording a tool span for failed argument validation, while keeping wrap_tool_execute as the single wrapper around the full tool execution lifecycle.

What the PR does well

  • Correct hook traversal — _record_tool_validation_error in tool_manager.py correctly walks CombinedCapability → WrapperCapability → Instrumentation, respects defer_loading + available_capability_ids, and deduplicates via seen: set[int]. Matches the existing _ctx_for_available_cap style used elsewhere.
  • Targeted span — Instrumentation._record_tool_validation_error only fires when validation actually failed (validated.args_valid is False), avoiding double spans for valid calls.
  • Doc updates — abstract.py docstring and capabilities.md table/diagram now describe the new wrap_tool_execute lifecycle accurately. The error-hooks section explicitly calls out tool execution as the exception to the general ordering.
  • Comprehensive tests — tests/test_logfire.py covers schema/validator/unknown failures, include_content on/off, wrapped_instrumentation on/off, and the deferred-loading interaction. tests/test_capabilities.py adds dedicated lifecycle ordering, onion-order preservation, and recovery tests.
  • Backward compatibility — wrap_validation_errors=False callers still get the raw exception path they expect; no behavior change for them.

Notes (not blocking)

The docstring of Instrumentation._record_tool_validation_error says "zero-width tool span" and the PR description uses "zero-width error span", but the implementation creates a regular start_as_current_span (with measurable duration). In the issue thread this was discussed as one of the alternatives to the full span; the author chose the full span. Either the docstring/PR description should be aligned with the chosen approach, or the implementation should actually be zero-width. Cosmetic, not behavioral.

The pre-existing wart that logfire.msg = "running tool: {tool_name}" is reused for validation-error spans (where no tool actually ran) is unchanged by this PR — it was the v1.94.0 behavior too, so restoring it is consistent.

Verdict

No HIGH or CRITICAL findings. Implementation is correct, tests are thorough, and the documented wrap_tool_execute lifecycle change matches the new code path.

@github-actions github-actions Bot left a comment

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.

Clean fix for #6555. The new _instrument_tool_call closure on ToolManager correctly wraps the full validate+execute lifecycle, restoring the pre-v1.95 span boundary around validation failures. Verified: validation errors (schema and validator-rejected args) now produce an execute_tool span with status ERROR and the retry prompt as gen_ai.tool.call.result. Deferred Instrumentation (wrapped via WrapperCapability) is gated on ctx.available_capability_ids and only traces when loaded. User wrap_tool_execute still wraps the complete execute lifecycle (before, body, error, after); on_tool_execute_error still triggers on tool-body exceptions; control-flow exceptions still propagate. Instrumentation no longer overrides wrap_tool_execute, so the OTel span is the outermost wrapper. Tests cover all three scenarios and docs reflect the new semantics.

@github-actions github-actions Bot left a comment

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.

Verified the fix restores the pre-v1.95 tool-span boundary: Instrumentation._wrap_tool_call now wraps the operation that either raises a stored validation error or executes the tool, and _execute_tool_call_impl is invoked inside the span. wrap_tool_execute semantics correctly expand to enclose before_tool_execute / on_tool_execute_error / after_tool_execute, matching the new docstring and the test_wrap_tool_execute_wraps_execute_lifecycle and test_tool_span_wraps_recovered_and_modified_lifecycle assertions. WrapperCapability and CombinedCapability delegate the new _wrap_tool_call hook symmetrically with the other lifecycle hooks. ToolRetryError raised from a failed validation is recorded as the tool-result attribute and the span is marked ERROR (handle_tool_control_flow=True), matching v1.94. No findings.

@adtyavrdhn
adtyavrdhn marked this pull request as ready for review July 20, 2026 09:53
@macroscopeapp

macroscopeapp Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved d0d09dc

This PR adds instrumentation spans for tool argument validation failures and refactors retry prompt creation into a centralized factory method. The changes are focused on observability/tracing without altering core execution behavior, with comprehensive test coverage.

You can customize Macroscope's approvability policy. Learn more.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e10408b51d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@coderabbitai

coderabbitai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Tool execution wrapping now covers both stored validation failures and validated execution. Capability hook contracts accept nullable tool definitions and arguments, while ToolManager places wrapping around the complete tool-call path and preserves skip, retry, error, and after-execution handling. Documentation explains the lifecycle changes, and capability and instrumentation tests cover validation failures, lifecycle ordering, deferred instrumentation, recovery, and span emission.

Possibly related PRs

  • pydantic/pydantic-ai#6576: Both changes address instrumentation and tool-span behavior for tool argument validation failures.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The implementation restores tool-span coverage for validation failures and full tool execution, matching #6555.
Out of Scope Changes check ✅ Passed The docs and tests support the lifecycle change, and no unrelated code changes stand out.
Title check ✅ Passed The title accurately summarizes the main change: moving tool-stage wrap hooks outward and restoring spans for failed validation.
Description check ✅ Passed The description matches the template with issue linkage and checklist section; the remaining unchecked items are non-critical.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/issue-6555-wrap-tool-execute

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/hooks.md (1)

158-158: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Same "all tool hooks" blanket statement conflicts with the new nullable tool_def note.

See consolidated comment anchored on docs/capabilities.md.

Also applies to: 174-177

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/hooks.md` at line 158, Update the tool-hook documentation around the
“All tool hooks receive” statement to account for nullable or absent tool_def
values, aligning it with the nullable tool_def guidance in docs/capabilities.md.
Apply the same clarification to the corresponding tool-hook parameter
descriptions around the later referenced section.
docs/capabilities.md (1)

1582-1582: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

"All tool hooks receive a tool_def parameter" now contradicts the new wrap_tool_execute nullability note a few lines down.

See consolidated comment (anchored here) covering both this file and docs/hooks.md.

Also applies to: 1595-1610

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/capabilities.md` at line 1582, Update the tool-hook documentation around
the ToolDefinition parameter and wrap_tool_execute nullability note so it no
longer claims every hook always receives a non-null tool_def. Align the
corresponding wording in docs/hooks.md, clearly documenting when tool_def may be
absent while preserving the existing behavior for hooks that do receive a
ToolDefinition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@docs/capabilities.md`:
- Line 1582: Update the tool-hook documentation around the ToolDefinition
parameter and wrap_tool_execute nullability note so it no longer claims every
hook always receives a non-null tool_def. Align the corresponding wording in
docs/hooks.md, clearly documenting when tool_def may be absent while preserving
the existing behavior for hooks that do receive a ToolDefinition.

In `@docs/hooks.md`:
- Line 158: Update the tool-hook documentation around the “All tool hooks
receive” statement to account for nullable or absent tool_def values, aligning
it with the nullable tool_def guidance in docs/capabilities.md. Apply the same
clarification to the corresponding tool-hook parameter descriptions around the
later referenced section.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 530b436f-d28f-46ee-bf35-a09834202b6b

📥 Commits

Reviewing files that changed from the base of the PR and between 7594270 and e10408b.

📒 Files selected for processing (10)
  • docs/capabilities.md
  • docs/hooks.md
  • pydantic_ai_slim/pydantic_ai/capabilities/abstract.py
  • pydantic_ai_slim/pydantic_ai/capabilities/combined.py
  • pydantic_ai_slim/pydantic_ai/capabilities/hooks.py
  • pydantic_ai_slim/pydantic_ai/capabilities/instrumentation.py
  • pydantic_ai_slim/pydantic_ai/capabilities/wrapper.py
  • pydantic_ai_slim/pydantic_ai/tool_manager.py
  • tests/test_capabilities.py
  • tests/test_logfire.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • pydantic/logfire (manual)
  • pydantic/pydantic-ai (manual)
  • pydantic/pydantic (auto-detected)
  • pydantic/unified-docs (auto-detected)

@github-actions github-actions Bot left a comment

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.

Request changes — unresolved instrumentation/skip interaction

The prior UNRESOLVED review from chatgpt-codex-connector (commit-referenced diff on tool_manager.py) still applies to the current code: when Instrumentation is enabled (auto-prepended as the outermost capability — see pydantic_ai_slim/pydantic_ai/agent/__init__.py:1335) and an inner capability's wrap_tool_execute raises SkipToolExecution, the exception is caught by Instrumentation._run_tool_span's except BaseException block (pydantic_ai_slim/pydantic_ai/capabilities/instrumentation.py:381), which records the span with StatusCode.ERROR and re-raises. The same exception is then caught at tool_manager.py:517 and converted into a successful e.result. Net effect: a tool the agent deliberately skipped produces ERROR-status telemetry — the opposite of the PR's intent (#6555 was about restoring correct tool-span coverage for failed validation, not about regressing successful-skip telemetry).

To verify locally, run the new test_skip_tool_execution test (tests/test_capabilities.py:5647) with Instrumentation added to the agent's capabilities= list and assert logfire.level_num < 17 on the resulting tool span — it will fail under the current code.

The proposed fix is to special-case SkipToolExecution inside Instrumentation._run_tool_span (treating it as control flow, like the existing (CallDeferred, ApprovalRequired) branch on lines 354-372). The companion test (tests/test_logfire.py) should be extended to cover both phases (before and wrap) alongside Instrumentation so this can't regress.

The rest of the PR — type-signature change on AbstractCapability.wrap_tool_execute, removal of the private _instrument_tool_call boundary, decorator-style Hooks validation-failure filtering at hooks.py:1096, and the new tests/test_logfire.py validation-failure span tests — looks correct. The public-API compatibility note in the PR description adequately flags the breaking change for AbstractCapability.wrap_tool_execute overrides, so once the instrumentation/skip interaction is fixed this should be ready.

…-tool-execute

# Conflicts:
#	docs/capabilities.md

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
docs/hooks.md (1)

177-179: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clarify nullable arguments for wrap_tool_execute.

The preceding text says args is always a validated dictionary, but AbstractCapability.wrap_tool_execute also wraps stored validation failures where tool_def and args may be None. Scope the “always” statement to decorator execution hooks and document the nullable inputs for direct capability overrides.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/hooks.md` around lines 177 - 179, Update the documentation around
AbstractCapability.wrap_tool_execute to limit the “args is always a validated
dictionary” statement to tool_execute decorator hooks, and explicitly document
that direct wrap_tool_execute overrides may receive None for tool_def and args
when handling stored validation failures.
docs/capabilities/custom.md (3)

630-630: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix the unresolved output-tool reference.

[output tools][pydantic_ai.output.ToolOutput] has no defined reference and triggers MD052. Link directly to ../output.md#tool-output or add the canonical reference definition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/capabilities/custom.md` at line 630, Update the output-tools link in the
prepare_output_tools documentation entry to resolve correctly by linking
directly to ../output.md#tool-output, or define and use the canonical reference
for pydantic_ai.output.ToolOutput. Keep the surrounding lifecycle description
unchanged.

Sources: Coding guidelines, Linters/SAST tools


568-590: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clarify the nullable tool_def exception.

“All tool hooks receive a ToolDefinition” conflicts with the following wrap_tool_execute contract, where tool_def may be None for unknown tools. State that all hooks receive the parameter, but only validation and non-wrapper execution hooks guarantee a non-null definition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/capabilities/custom.md` around lines 568 - 590, Update the introductory
statement and execution-hook documentation around AbstractCapability so they
clarify that all hooks receive a tool_def parameter, but wrap_tool_execute may
receive None for unknown tools. Explicitly state that validation hooks and
non-wrapper execution hooks guarantee a non-null ToolDefinition, while
preserving the existing nullable contract for wrap_tool_execute.

371-377: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Escape union separators inside Markdown tables.

The literal | characters are parsed as extra table columns, causing MD056 and malformed rendered tables. Escape them as \| (as already done for wrap_tool_execute on Line 589).

Also applies to: 577-577, 612-612

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/capabilities/custom.md` around lines 371 - 377, Escape the literal union
separators in the affected return-type cells of the capability method table,
including the rows for get_toolset(), get_wrapper_toolset(), get_instructions(),
get_model_settings(), get_model(), and resolve_model_id(), plus the additional
referenced rows. Preserve the displayed type unions while formatting each pipe
as \|, consistent with the existing wrap_tool_execute entry.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@docs/capabilities/custom.md`:
- Line 630: Update the output-tools link in the prepare_output_tools
documentation entry to resolve correctly by linking directly to
../output.md#tool-output, or define and use the canonical reference for
pydantic_ai.output.ToolOutput. Keep the surrounding lifecycle description
unchanged.
- Around line 568-590: Update the introductory statement and execution-hook
documentation around AbstractCapability so they clarify that all hooks receive a
tool_def parameter, but wrap_tool_execute may receive None for unknown tools.
Explicitly state that validation hooks and non-wrapper execution hooks guarantee
a non-null ToolDefinition, while preserving the existing nullable contract for
wrap_tool_execute.
- Around line 371-377: Escape the literal union separators in the affected
return-type cells of the capability method table, including the rows for
get_toolset(), get_wrapper_toolset(), get_instructions(), get_model_settings(),
get_model(), and resolve_model_id(), plus the additional referenced rows.
Preserve the displayed type unions while formatting each pipe as \|, consistent
with the existing wrap_tool_execute entry.

In `@docs/hooks.md`:
- Around line 177-179: Update the documentation around
AbstractCapability.wrap_tool_execute to limit the “args is always a validated
dictionary” statement to tool_execute decorator hooks, and explicitly document
that direct wrap_tool_execute overrides may receive None for tool_def and args
when handling stored validation failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ff6daa0-f71b-42a0-b57d-3f6c2481d5f7

📥 Commits

Reviewing files that changed from the base of the PR and between 4dfb62c and 1085001.

📒 Files selected for processing (8)
  • docs/capabilities/custom.md
  • docs/hooks.md
  • pydantic_ai_slim/pydantic_ai/capabilities/abstract.py
  • pydantic_ai_slim/pydantic_ai/capabilities/combined.py
  • pydantic_ai_slim/pydantic_ai/capabilities/instrumentation.py
  • pydantic_ai_slim/pydantic_ai/tool_manager.py
  • tests/test_capabilities.py
  • tests/test_logfire.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • pydantic/logfire (manual)
  • pydantic/pydantic-ai (manual)
  • pydantic/pydantic (auto-detected)
  • pydantic/unified-docs (auto-detected)
🚧 Files skipped from review as they are similar to previous changes (6)
  • pydantic_ai_slim/pydantic_ai/capabilities/instrumentation.py
  • pydantic_ai_slim/pydantic_ai/capabilities/combined.py
  • pydantic_ai_slim/pydantic_ai/capabilities/abstract.py
  • pydantic_ai_slim/pydantic_ai/tool_manager.py
  • tests/test_capabilities.py
  • tests/test_logfire.py

@github-actions github-actions Bot left a comment

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.

REQUEST_CHANGES — re-reviewing the diff against the previous unresolved thread.

The one substantive blocker (HIGH severity) is already flagged by chatgpt-codex-connector at pydantic_ai_slim/pydantic_ai/tool_manager.py:521: the new outer wrap_tool_execute boundary routes SkipToolExecution through Instrumentation._run_tool_span, where the except BaseException branch (instrumentation.py:387) marks the span ERROR and records the exception, even though execute_tool_call immediately converts that same exception into a successful replacement result. I traced the path end-to-end (the CombinedCapability chain places Instrumentation as the outermost cap per CapabilityOrdering(position=outermost)), and the bug reproduces as described — telemetry for skipped/denied tools reports error status and logs the SkipToolExecution as an escaped exception.

Per the review conventions I am not duplicating that finding inline; the existing thread is the right place for it. The PR remains blocked until SkipToolExecution (and by extension CallDeferred/ApprovalRequired already handled) is either special-cased in _run_tool_span so the span closes cleanly, or the wrap_tool_execute boundary is restructured to keep the skipped path outside the instrumented action.

The rest of the diff looks sound on a full-file re-read:

  • AbstractCapability.wrap_tool_execute defaults to return await handler(args) — args=None correctly raises the stored validation_error via _execute_tool_call_impl.
  • hooks.py short-circuits on tool_def is None or args is None, preserving the documented hooks.on.tool_execute valid-call-only contract.
  • WrapperCapability and CombinedCapability correctly thread the optional types.
  • No double-counting of ctx.usage.tool_calls: _execute_tool_call_impl catches SkipToolExecution from before_tool_execute/innermost handler; execute_tool_call only catches it from cap.wrap_tool_execute itself.
  • New tests (test_tool_argument_validation_error_emits_span, test_wrapped_deferred_instrumentation_only_traces_when_loaded, test_tool_span_wraps_recovered_and_modified_lifecycle, test_wrap_tool_execute_wraps_execute_lifecycle, parametrized test_skip_tool_execution) correctly cover schema/validator/unknown failure sources, deferred loading, and lifecycle ordering.

One small doc nit (LOW): the VerboseLogging example in docs/capabilities/custom.md still does print(f" Tool call: {call.tool_name}({args})") against the new args: dict[str, Any] | None — when wrap_tool_execute is invoked for a stored validation failure this will print name(None). Worth a quick update alongside the rest of the docs to avoid surprising copy-paste users.

@github-actions github-actions Bot left a comment

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.

Reviewed at 25a08edda9494e3cbd94d5666edc919ed12800a8.

No new findings — the surviving concerns from prior review threads (SkipToolExecution span handling, deferral scope, redaction asymmetry between validation and execution spans, exception.escaped bool/string alignment, from __future__ import annotations in the new helper, and the wrap_tool_validate signature) are all addressed at the current head. The implementation correctly restores the v1.94 tool-span behavior for failed argument validation, scopes the include_content=False redaction to the validation path with an explicit rationale comment, and shares retry-prompt construction so telemetry matches the prompt sent to the model. Tests cover both failure_source × include_content paths and the recovery-no-error-span contract for both capability orderings.

Generated by 🔎 CI Review

… and `pydantic_ai.tool.failure_stage` attribute
@macroscopeapp
macroscopeapp Bot dismissed their stale review August 5, 2026 09:41

Dismissing prior approval to re-evaluate 4bd0e03

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Aug 5, 2026

@github-actions github-actions Bot left a comment

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.

Reviewed at 4bd0e0339cde7be99a7edaf9ec0bd1533af910e2.

The head commit is a small, additive polish on top of an already-thoroughly-reviewed change: it adds pydantic_ai.tool.failure_stage='validation' and the invalid tool call: <name> logfire.msg to the failed-validation span so backends can distinguish it from execution failures, with matching test assertions on both the failure span and the subsequent successful retry. No new behavior, no new public API, no risk to existing telemetry shape. Local pytest tests/test_logfire.py and pyright on the touched files are clean; the prior resolved threads (redaction under include_content=False, exception.escaped boolean alignment, from __future__ import annotations in _tool_retry.py) all still hold against this commit.

Generated by 🔎 CI Review

@macroscopeapp
macroscopeapp Bot dismissed their stale review August 5, 2026 10:52

Dismissing prior approval to re-evaluate fd063e9

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Aug 5, 2026
@macroscopeapp
macroscopeapp Bot dismissed their stale review August 5, 2026 10:56

Dismissing prior approval to re-evaluate 8a957f4

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Aug 5, 2026
@macroscopeapp
macroscopeapp Bot dismissed their stale review August 7, 2026 04:31

Dismissing prior approval to re-evaluate bc85385

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Aug 7, 2026

@github-actions github-actions Bot left a comment

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.

Reviewed at bc8538583def4827f36ca039e20d606a6bb305d5.

The PR restores the v1.94.0 failed-validation tool-span contract cleanly: shared retry-prompt construction (pydantic_ai/_tool_retry.py) keeps telemetry aligned with the prompt the model receives; recovery capabilities run before Instrumentation.on_tool_validate_error via CombinedCapability's reverse-order loop; and include_content=False redaction omits the rejected values. Tests cover both schema and validator failure sources, both capability orderings, and raw-mode dispatch. No new findings.

Generated by 🔎 CI Review

@macroscopeapp
macroscopeapp Bot dismissed their stale review August 7, 2026 05:24

Dismissing prior approval to re-evaluate 61a11ea

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Aug 7, 2026

@github-actions github-actions Bot left a comment

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.

Reviewed at 61a11ea852512ee5c117f8c66912eb1cf57ecac8.

This is a well-scoped fix for the regression tracked as #6555. Key observations:

  • The shared retry_prompt_from_error helper in the new _retry_prompt.py module is now the single source of retry-prompt text used by _output.py, tool_manager.py, and capabilities/instrumentation.py, so the span's recorded prompt and the message sent to the model cannot drift apart again.
  • Instrumentation.on_tool_validate_error correctly emits a zero-width ERROR span only when no recovery capability returns validated args; the recovered path produces no error span (test_recovered_tool_validation_emits_no_error_span).
  • The include_content=False branch records exception.type only (with a boolean exception.escaped and a comment explaining why the validation path differs from the execution path), and the assert 'private-invalid-value' not in str(...) line pins that redaction.
  • Both ValidationError (schema) and ModelRetry (validator) failure sources are parametrized in test_tool_argument_validation_failure_emits_span; test_raw_mode_validation_failure_emits_span covers handle_call(wrap_validation_errors=False) from a sandboxed dispatcher.

The deliberate scope limit on the redaction asymmetry (validation redaction only, execution redaction deferred) is explicitly called out in the code comment, so future readers won't accidentally widen it without intent.

No new findings.

Generated by 🔎 CI Review

@macroscopeapp
macroscopeapp Bot dismissed their stale review August 10, 2026 05:23

Dismissing prior approval to re-evaluate d0d09dc

@adtyavrdhn
adtyavrdhn temporarily deployed to ui-security-review August 10, 2026 05:23 — with GitHub Actions Inactive
@adtyavrdhn
adtyavrdhn merged commit 574f3f8 into main Aug 10, 2026
99 of 102 checks passed
@adtyavrdhn
adtyavrdhn deleted the codex/issue-6555-wrap-tool-execute branch August 10, 2026 06:01
dsfaccini added a commit that referenced this pull request Aug 10, 2026
Ports the tool-span construction #6601 added onto the retried `ToolReturnPart`
this branch introduces: `_output.build_retried_tool_return` is now the single
builder the agent loop and the validation-error span share, so the span keeps
recording exactly what the model receives. `RetryPromptPart.from_error` stays as
the constructor for user code answering a deferred call with a retry of its own.
736-c41-2c1-e464fc974 added a commit to Swiss-Armed-Forces/Loom that referenced this pull request Sep 15, 2026
This MR contains the following updates:

| Package | Type | Update | Change | OpenSSF |
|---|---|---|---|---|
| [numpy](https://github.com/numpy/numpy) ([changelog](https://numpy.org/doc/stable/release)) | dependencies | patch | `2.5.2` → `2.5.3` | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/numpy/numpy/badge)](https://securityscorecards.dev/viewer/?uri=github.com/numpy/numpy) |
| [openai](https://github.com/openai/openai-python) | dependencies | patch | `2.52.0` → `2.52.1` | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/openai/openai-python/badge)](https://securityscorecards.dev/viewer/?uri=github.com/openai/openai-python) |
| [pydantic](https://github.com/pydantic/pydantic) ([changelog](https://docs.pydantic.dev/latest/changelog/)) | dependencies | patch | `2.13.4` → `2.13.5` | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/pydantic/pydantic/badge)](https://securityscorecards.dev/viewer/?uri=github.com/pydantic/pydantic) |
| [pydantic-ai](https://github.com/pydantic/pydantic-ai) ([changelog](https://github.com/pydantic/pydantic-ai/releases)) | dependencies | patch | `2.27.0` → `2.27.1` | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/pydantic/pydantic-ai/badge)](https://securityscorecards.dev/viewer/?uri=github.com/pydantic/pydantic-ai) |
| [types-requests](https://github.com/python/typeshed) ([changelog](https://github.com/typeshed-internal/stub_uploader/blob/main/data/changelogs/requests.md)) | dependencies | patch | `2.33.0.20260712` → `2.33.0.20260906` | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/python/typeshed/badge)](https://securityscorecards.dev/viewer/?uri=github.com/python/typeshed) |
| [uvicorn](https://github.com/Kludex/uvicorn) ([changelog](https://uvicorn.dev/release-notes)) | dependencies | patch | `0.52.3` → `0.52.4` | [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Kludex/uvicorn/badge)](https://securityscorecards.dev/viewer/?uri=github.com/Kludex/uvicorn) |

---

### Release Notes

<details>
<summary>numpy/numpy (numpy)</summary>

### [`v2.5.3`](https://github.com/numpy/numpy/releases/tag/v2.5.3): (Sep 6, 2026)

[Compare Source](numpy/numpy@v2.5.2...v2.5.3)

### NumPy 2.5.3 Release Notes

The NumPy 2.5.3 is a patch release that fixes bugs discovered after the 2.5.2
release. Apart from the usual bug and maintenance work, there are a number of
StringDType related fixes for problems discovered during the ongoing string
work in the main branch.

This release supports Python versions 3.12-3.15

#### Changes

- Casting a fixed-width byte string array (`np.bytes_`) to `StringDType`
  now raises `TypeError` when the bytes are not valid UTF-8. Previously the
  invalid bytes were stored as-is and later caused undefined behavior in
  string operations.

  ([gh-32296](numpy/numpy#32296))

- `MaskedArray._fill_value` would become stale when ufuncs that change dtype
  left the result holding a fill\_value typed for the old dtype. The mismatch
  was silent until something later called `_check_fill_value`, such as
  `.view()`, and then a `TypeError` would be raised. Now, when the copied
  fill\_value is no longer valid for the new dtype, fall back to the
  default fill\_value for that dtype instead of propagating the stale value.
  This may raise a `ComplexWarning` if the fill\_value is complex and the
  new dtype is real.

  ([gh-32423](numpy/numpy#32423))

#### Contributors

A total of 9 people contributed to this release. People with a "+" by their
names contributed a patch for the first time.

- Charles Harris
- Iason Krommydas
- James Davies +
- Joren Hammudoglu
- Maanas Arora
- Matti Picus
- Nathan Goldbaum
- Shikhar Goel +
- Yeonho Kim +

#### Pull requests merged

A total of 27 pull requests were merged for this release.

- [#&#8203;32235](numpy/numpy#32235): MAINT: Prepare 2.5.x for further development
- [#&#8203;32289](numpy/numpy#32289): BUG: raise ValueError when reading into record array with references...
- [#&#8203;32290](numpy/numpy#32290): BUG: avoid uninitialized memory access / NULL-pointer deref in...
- [#&#8203;32291](numpy/numpy#32291): TYP: fix `np.random.{get,set}_bit_generator` implicit re-exports...
- [#&#8203;32292](numpy/numpy#32292): BUG: don't assume strides are a multiple of itemsize in stringdtype...
- [#&#8203;32293](numpy/numpy#32293): CI: fix ccache CC override, add CXX in mac Conda CI ([#&#8203;32285](numpy/numpy#32285))
- [#&#8203;32303](numpy/numpy#32303): BUG: Fix ref leak in \[convert\_from\_type]{#convert\_from\_type} for custom scalar types...
- [#&#8203;32304](numpy/numpy#32304): BUG: fix a number of issues around iterators and StringDType...
- [#&#8203;32326](numpy/numpy#32326): TST: avoid allocating huge tuple of arrays in concatenate test...
- [#&#8203;32338](numpy/numpy#32338): BUG: avoid possible UB in 'safe' multiplication helpers ([#&#8203;32294](numpy/numpy#32294))
- [#&#8203;32339](numpy/numpy#32339): MAINT: use `PyObject_` functions instead of raw `PyArray_ ones` ([#&#8203;32331](numpy/numpy#32331))
- [#&#8203;32378](numpy/numpy#32378): BUG: validate UTF-8 and harden StringDType bounds handling ([#&#8203;32296](numpy/numpy#32296))
- [#&#8203;32380](numpy/numpy#32380): BUG: fix two error handling mistakes in stringdtype replace loop...
- [#&#8203;32381](numpy/numpy#32381): BUG: fix visibility annotations for functions in StringDType...
- [#&#8203;32384](numpy/numpy#32384): MAINT: Update ml\_dtypes pin to 8/21/2026.
- [#&#8203;32385](numpy/numpy#32385): MAINT: Update numpy/\_core/src/umath/svml
- [#&#8203;32410](numpy/numpy#32410): MAINT: skip failing cython limited API tests on Cython 3.3.0...
- [#&#8203;32427](numpy/numpy#32427): BUG: close duplicated file descriptor if fdopen fails ([#&#8203;32386](numpy/numpy#32386))
- [#&#8203;32428](numpy/numpy#32428): MAINT: add missing space in warning and error messages ([#&#8203;32405](numpy/numpy#32405))
- [#&#8203;32430](numpy/numpy#32430): BUG: fix error handling in StringDType to fixed-width bytes case...
- [#&#8203;32441](numpy/numpy#32441): MAINT: Only run nightly BLAS tests on main.
- [#&#8203;32471](numpy/numpy#32471): BUG: fix memory leak in StringDType creation error path ([#&#8203;32470](numpy/numpy#32470))
- [#&#8203;32477](numpy/numpy#32477): BUG: fix stale fill\_value after ufuncs change MaskedArray dtype...
- [#&#8203;32478](numpy/numpy#32478): MAINT: exit deadlock tests quickly on slow hardware ([#&#8203;32466](numpy/numpy#32466))
- [#&#8203;32481](numpy/numpy#32481): BUG: Backport StringDType byteorder fixes
- [#&#8203;32506](numpy/numpy#32506): DOC: use static scipy doc site for intershpinx ([#&#8203;32503](numpy/numpy#32503))
- [#&#8203;32509](numpy/numpy#32509): BUG: fix crash in ufunc.resolve\_dtypes with a Python scalar type...

</details>

<details>
<summary>openai/openai-python (openai)</summary>

### [`v2.52.1`](https://github.com/openai/openai-python/blob/HEAD/CHANGELOG.md#2521-2026-07-31)

[Compare Source](openai/openai-python@v2.52.0...v2.52.1)

Full Changelog: [v2.52.0...v2.52.1](openai/openai-python@v2.52.0...v2.52.1)

##### Chores

- **ci:** pin setup-uv v5 to its underlying commit ([#&#8203;3560](openai/openai-python#3560)) ([cbdc98b](openai/openai-python@cbdc98b))

</details>

<details>
<summary>pydantic/pydantic (pydantic)</summary>

### [`v2.13.5`](https://github.com/pydantic/pydantic/releases/tag/v2.13.5)

[Compare Source](pydantic/pydantic@v2.13.4...v2.13.5)

#### v2.13.5 (2026-08-28)

##### What's Changed

##### Fixes

- Allow reuse of validators when plugins are set by [@&#8203;Viicos](https://github.com/Viicos) in [#&#8203;13535](pydantic/pydantic#13535)
- Fix missing GC traversal on some `pydantic-core` struct fields by [@&#8203;Viicos](https://github.com/Viicos) in [#&#8203;13624](pydantic/pydantic#13624)
- Fix missing GC traversal in `pydantic-core` for `GeneralFieldsSerializer` by [@&#8203;Viicos](https://github.com/Viicos) in [#&#8203;13629](pydantic/pydantic#13629)
- Count validated model fields once in smart unions by [@&#8203;tamird](https://github.com/tamird) in [#&#8203;13731](pydantic/pydantic#13731)

</details>

<details>
<summary>pydantic/pydantic-ai (pydantic-ai)</summary>

### [`v2.27.1`](https://github.com/pydantic/pydantic-ai/releases/tag/v2.27.1): (2026-08-10)

[Compare Source](pydantic/pydantic-ai@v2.27.0...v2.27.1)

##### 🛡️ Security

This release fixed an information-disclosure issue: retry-prompt content (validation feedback sent back to the model, which can quote invalid values from its output) was not redacted by `InstrumentationSettings(include_content=False)` when the retry was not tied to a tool call. Now disclosed as [GHSA-3gh4-cghq-f8v4](GHSA-3gh4-cghq-f8v4) (low). Fixed here in `2.27.1` ([#&#8203;7357](pydantic/pydantic-ai#7357)); v1 users should upgrade to `1.107.4` or later.

<!-- Release notes generated using configuration in .github/release.yml at main -->

#### What's Changed

##### 🐛 Bug Fixes

- Restore tool spans for failed argument validation by [@&#8203;adtyavrdhn](https://github.com/adtyavrdhn) in [#&#8203;6601](pydantic/pydantic-ai#6601)
- Fix `XaiStreamedResponse` finish\_reason mapping for streaming responses by [@&#8203;pydanty](https://github.com/pydanty)\[bot] in [#&#8203;6814](pydantic/pydantic-ai#6814)
- Point offline web UI hosting at the self-contained chat UI build by [@&#8203;dsfaccini](https://github.com/dsfaccini) in [#&#8203;7349](pydantic/pydantic-ai#7349)
- Allow adaptive thinking with Tool Output and forced tool choice on Anthropic by [@&#8203;pydanty](https://github.com/pydanty)\[bot] in [#&#8203;7200](pydantic/pydantic-ai#7200)
- Gate `RetryPromptPart` OpenTelemetry content on `include_content` by [@&#8203;sean-kim05](https://github.com/sean-kim05) in [#&#8203;7357](pydantic/pydantic-ai#7357)

**Full Changelog**: <pydantic/pydantic-ai@v2.27.0...v2.27.1>

</details>

<details>
<summary>Kludex/uvicorn (uvicorn)</summary>

### [`v0.52.4`](https://github.com/Kludex/uvicorn/releases/tag/0.52.4): Version 0.52.4

[Compare Source](Kludex/uvicorn@0.52.3...0.52.4)

##### Fixed

- Remove duplicate `Date` headers from accepted WebSocket handshakes with `websockets-sansio` ([#&#8203;3078](Kludex/uvicorn#3078))

**Full Changelog**: <Kludex/uvicorn@0.52.3...0.52.4>

</details>

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zOS4yIiwidXBkYXRlZEluVmVyIjoiNDQuOTAuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiZGVwZW5kZW5jaWVzIiwicmVub3ZhdGUiXX0=-->

See merge request swiss-armed-forces/cyber-command/cea/loom!769

Co-authored-by: Loom MR Pipeline Trigger <group_103951964_bot_9504bb8dead6d4e406ad817a607f24be@noreply.gitlab.com>
Co-authored-by: shrewd-laidback palace <shrewd-laidback-palace-736-c41-2c1-e464fc974@swiss-armed-forces-open-source.ch>

This branch was previously deployed

1 inactive deployment
ui-security-review — d0d09dcf Deployed Aug 10, 2026 by adtyavrdhn via activation #4137
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Report that something isn't working, or PR implementing a fix size: M Medium PR (101-500 weighted lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression since v1.95.0: tool calls that fail argument validation no longer produce tool spans

1 participant