Skip to content

fix: re-raise control-flow exceptions in ToolInterceptCapability.wrap_tool_execute - #129

Merged
Leoyzen merged 1 commit into
develop/agenticfrom
fix/tool-intercept-abort
Jul 8, 2026
Merged

fix: re-raise control-flow exceptions in ToolInterceptCapability.wrap_tool_execute#129
Leoyzen merged 1 commit into
develop/agenticfrom
fix/tool-intercept-abort

Conversation

@Leoyzen

@Leoyzen Leoyzen commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Problem

The unified hook system refactor (fa326ff, PR #125) introduced ToolInterceptCapability.wrap_tool_execute() with a broad except Exception that swallowed all exceptions including control-flow ones:

  • RunAbortedError — raised when user cancels elicitation/question (e.g. question_for_user cancel). Should propagate to NativeTurn.execute()'s except RunAbortedError handler to abort the run. Instead it was converted to a ToolReturn error response, causing the agent to continue executing after cancel.
  • ModelRetry — pydantic-ai's retry signal for fixable tool errors. Should propagate to the pydantic-ai framework. Instead the LLM received it as a tool success with error text — semantically different behavior.
  • ToolSkippedError — raised when a pre-tool hook denies execution. Should propagate as a skip signal. Instead converted to a tool error.

Evidence

From ~/Library/Logs/agentpool/acp.log:

[warning] Tool execution failed  error='User cancelled the questionnaire.' error_type=RunAbortedError tool_name=question_for_user

After the warning, the agent continued: CallToolsNode → ModelRequestNode (LLM got the error and generated another request).

Fix

Add except (RunAbortedError, ToolSkippedError, ModelRetry): raise before the generic except Exception block in wrap_tool_execute().

Tests

3 new regression tests in tests/agents/native_agent/test_tool_intercept_capability.py:

Test Verifies
test_wrap_tool_execute_reraises_run_aborted_error RunAbortedError is re-raised, not swallowed
test_wrap_tool_execute_reraises_model_retry ModelRetry is re-raised, not swallowed
test_wrap_tool_execute_reraises_tool_skipped_error ToolSkippedError is re-raised, not swallowed

Existing test_wrap_tool_execute_catches_exception (verifying normal ValueError is still caught) continues to pass. All 16 tests in the file pass, plus 11 question abort regression tests.

Affected Exception Sources

Exception Raised by Impact when swallowed
RunAbortedError question_for_user.py:226, tool_impls/question/tool.py:103, agents/context.py:436 Agent continues after user cancel
ModelRetry 8 call sites across codemode, fsspec, subagent, question tools LLM gets error text instead of retry signal
ToolSkippedError mcp_server/tool_bridge.py:524 Skip signal becomes error response

@Leoyzen
Leoyzen requested a review from Million-mo July 8, 2026 08:09

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request ensures that control-flow exceptions (RunAbortedError, ToolSkippedError, and ModelRetry) raised during tool execution are re-raised and propagated rather than being swallowed as generic tool errors. Unit tests are added to verify this behavior. The reviewer suggests also propagating other critical control-flow exceptions, such as CallDeferred, ApprovalRequired, and ChainAbortedError, to prevent breaking deferred execution and approval flows.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/agentpool/agents/native_agent/tool_intercept.py
…_tool_execute

The unified hook system refactor (fa326ff, PR #125) introduced
ToolInterceptCapability.wrap_tool_execute() with a broad
except Exception that swallowed all exceptions including control-flow
ones. This caused the agent to continue executing after user cancel,
broke deferred execution, approval flows, and LLM retry signals.

Re-raised exceptions (mirrors pydantic-ai's own contract in
_run_execute_hooks, tool_manager.py:325-328):

Pydantic-AI control-flow:
- CallDeferred — deferred execution signal from elicitation/MCP tools
- ApprovalRequired — human-in-the-loop approval signal
- ToolRetryError — what ModelRetry becomes after _raw_execute converts it
  (the actual retry signal in normal flow)
- ModelRetry — safety net for wrap_validation_errors=False edge case

AgentPool control-flow:
- RunAbortedError — user cancel/timeout, propagates to NativeTurn.execute()
- ToolSkippedError — pre-tool hook deny, propagates as skip signal

Not included (verified by Oracle analysis):
- ChainAbortedError — never raised anywhere in codebase
- JobError (base class) — too broad, catches unrelated JobRegistrationError

Adds 6 regression tests verifying each exception type is re-raised.
@Leoyzen
Leoyzen force-pushed the fix/tool-intercept-abort branch from 184b9c2 to fc0090c Compare July 8, 2026 08:30
@Leoyzen
Leoyzen merged commit 17632b2 into develop/agentic Jul 8, 2026
9 checks passed
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.

1 participant