Skip to content

Conversation

@bigeagle
Copy link
Contributor

@bigeagle bigeagle commented Jan 28, 2026

Description

In kosong SimpleToolset.handle, all exceptions are caught and re-wrapped with ToolRuntimeError, this erases the real exception and stopped me from handling the exceptions.

I added an catch_exception param (default to True).

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked the related issue, if any.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have run make gen-changelog to update the changelog.
  • I have run make gen-docs to update the user documentation.

Open with Devin

Copilot AI review requested due to automatic review settings January 28, 2026 10:21
Copy link

@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: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional flags.

Open in Devin Review

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an opt-out mechanism in SimpleToolset.handle() to avoid wrapping tool execution exceptions into ToolRuntimeError, enabling callers to observe underlying exceptions directly.

Changes:

  • Extends SimpleToolset.handle() with a catch_exception parameter (default True).
  • Conditionally re-wraps tool exceptions as ToolRuntimeError when catch_exception=True, otherwise re-raises.
  • Threads catch_exception into the spawned asyncio task.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

return [tool.base for tool in self._tool_dict.values()]

def handle(self, tool_call: ToolCall) -> HandleResult:
def handle(self, tool_call: ToolCall, catch_exception: bool = True) -> HandleResult:
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

New behavior via catch_exception isn’t covered by existing tests. There are already unit tests for SimpleToolset.handle behavior (e.g., packages/kosong/tests/test_tool_call.py), so please add a test that exercises catch_exception=False and asserts the expected outcome (either exception propagation or the chosen non-raising alternative) to prevent regressions.

Copilot uses AI. Check for mistakes.
Comment on lines 110 to +114
except Exception as e:
return ToolResult(tool_call_id=tool_call.id, return_value=ToolRuntimeError(str(e)))
if catch_exception:
return ToolResult(tool_call_id=tool_call.id, return_value=ToolRuntimeError(str(e)))
else:
raise
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

catch_exception=False causes _call to re-raise arbitrary exceptions (line 114), which violates the Toolset.handle contract that it “MUST NOT raise any exception except for asyncio.CancelledError” (packages/kosong/src/kosong/tooling/init.py:351-353). This will also break kosong.step because future_done_callback only catches asyncio.CancelledError, so other exceptions will surface as unhandled task/callback exceptions (packages/kosong/src/kosong/init.py:136-143). Consider keeping handle non-raising and instead add a hook/strategy to customize how exceptions are converted into a ToolReturnValue (e.g., allow passing an error-mapper function) so callers can preserve the underlying exception info without throwing from the task.

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +116
async def _call(catch_exception: bool = catch_exception) -> ToolResult:
try:
ret = await tool.call(arguments)
return ToolResult(tool_call_id=tool_call.id, return_value=ret)
except Exception as e:
return ToolResult(tool_call_id=tool_call.id, return_value=ToolRuntimeError(str(e)))
if catch_exception:
return ToolResult(tool_call_id=tool_call.id, return_value=ToolRuntimeError(str(e)))
else:
raise

return asyncio.create_task(_call())
return asyncio.create_task(_call(catch_exception))
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The inner _call defines catch_exception as a default argument (line 106) and then the task invocation also passes catch_exception (line 116). This is redundant and makes the control flow harder to follow. Prefer a single source of truth (either close over the outer variable and call _call() with no args, or remove the default and require _call(catch_exception) everywhere).

Copilot uses AI. Check for mistakes.
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.

2 participants