Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 27 additions & 15 deletions src/kimi_cli/soul/toolset.py

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.

🚩 HookEngine has a dedicated fire_and_forget_trigger helper that was not used here

The HookEngine class at src/kimi_cli/hooks/engine.py:93-115 has a fire_and_forget_trigger() method specifically designed to keep strong references to background hook tasks (preventing GC collection). The old code manually created tasks with asyncio.create_task() and added done callbacks, which was the pattern this helper replaced. The new code doesn't need this helper since it awaits the result directly, but it's worth noting the old code didn't use this helper either — it predates it. The PostToolUseFailure hook at line 402-418 still uses the old manual pattern rather than fire_and_forget_trigger().

(Refers to lines 402-418)

Open in Devin Review

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It's pre-existing technical debt, not something this PR introduced.

Original file line number Diff line number Diff line change
Expand Up @@ -458,22 +458,34 @@ async def _call():
dup_type="cross_step" if is_cross_step_dup else "normal",
)

# --- PostToolUse (fire-and-forget) ---
_hook_task = asyncio.create_task(
self._hook_engine.trigger(
"PostToolUse",
matcher_value=tool_name,
input_data=events.post_tool_use(
session_id=_get_session_id(),
cwd=str(Path.cwd()),
tool_name=tool_name,
tool_input=tool_input_dict,
tool_output=str(ret)[:2000],
tool_call_id=tool_call.id,
),
)
# --- PostToolUse (awaited, not fire-and-forget) ---
# Hooks run after the tool completes. Their stderr is appended
# to the tool result message so the LLM can see and act on it.
hook_results = await self._hook_engine.trigger(
"PostToolUse",
matcher_value=tool_name,
input_data=events.post_tool_use(
session_id=_get_session_id(),
cwd=str(Path.cwd()),
tool_name=tool_name,
tool_input=tool_input_dict,
tool_output=str(ret)[:2000],
tool_call_id=tool_call.id,
),
)
_hook_task.add_done_callback(lambda t: t.exception() if not t.cancelled() else None)

# Collect non-empty stderr from hooks for LLM visibility
hook_stderr_lines: list[str] = []
for hr in hook_results:
if hr.stderr.strip():
hook_stderr_lines.append(hr.stderr.strip())

if hook_stderr_lines:
hook_output = "\n".join(hook_stderr_lines)
if ret.message:
ret.message = f"{ret.message}\n\n[post-tool-use-hooks]\n{hook_output}"
else:
ret.message = f"[post-tool-use-hooks]\n{hook_output}"

return ToolResult(tool_call_id=tool_call.id, return_value=ret)

Expand Down
Loading