[BUG] post_tool_call hook does not pass turn_id — turn-counting plugins silently no-op
Bug Description
The post_tool_call plugin hook invocation in model_tools.py does not include turn_id in its kwargs. Plugins that rely on turn_id for per-turn deduplication or turn counting silently receive an empty string, causing them to under-count turns and never trigger their logic.
Environment
- OS: WSL2 (Ubuntu 22.04) on Windows 11
- Python: 3.11.15
- Hermes Version: v0.18.0 (carried commit, based on upstream main)
- Platform: WeCom (Enterprise WeChat) gateway
Root Cause
In model_tools.py:996-1004, the invoke_hook("post_tool_call", ...) call passes:
invoke_hook(
"post_tool_call",
tool_name=function_name,
args=function_args,
result=result,
task_id=task_id or "",
session_id=session_id or "",
tool_call_id=tool_call_id or "",
duration_ms=duration_ms,
)
turn_id is not passed. Furthermore, handle_function_call() (line 802) does not even accept a turn_id parameter — it is not available anywhere in the dispatch chain.
The _DEFAULT_PAYLOADS in hermes_cli/hooks.py:120-128 confirms this: the post_tool_call test payload contains tool_name, args, session_id, task_id, tool_call_id, result, duration_ms — but no turn_id.
Impact
Any plugin that uses turn_id for turn deduplication or counting will silently malfunction. Example — our ASH (Auto Session Handoff) plugin:
def _on_post_tool_call(*, tool_name, args, result, session_id="", turn_id="", **kwargs):
if turn_id and turn_id in _turn_counters[session_id]:
return # dedup
if turn_id:
_turn_counters[session_id].add(turn_id)
turn_count = len(_turn_counters[session_id])
if turn_count < 8:
return # ← ALWAYS returns here because turn_count is always 0
Since turn_id is always "", the counter never increments, turn_count stays at 0, and the plugin's auto-trigger logic is dead code. The plugin appears loaded and registered (logs show "ASH v1.0.0 loaded"), but never fires its core function. This is a silent failure — no error, no warning, no log entry.
Log Evidence
2026-07-07 15:01:53,197 INFO qijing-ash: [ADE-plugin-033] ASH v1.0.0 loaded — Auto Session Handoff protocol active
2026-07-07 16:16:35,031 INFO [20260707_144151_3a4c4ec6] qijing-ash: [ASH] 🔁 检测到未完成任务: task_test_ses_20260707_120253
The plugin loads successfully and on_session_start hook fires correctly, but post_tool_call never triggers a handoff across 27 invocations over 12 hours — because turn_id is always empty.
Related Issues
These confirm a systemic pattern of hook parameters being silently dropped in the dispatch chain.
Proposed Fix
Option A (minimal): Add turn_id to the invoke_hook call in model_tools.py. Requires threading turn_id from run_agent.py through handle_function_call().
Option B (no upstream change needed): Plugins that need turn counting should query state.db directly (e.g., SELECT COUNT(*) FROM messages WHERE role='user' AND session_id=?) rather than relying on turn_id from the hook payload. This is the workaround we applied locally.
Willing to Submit PR
Yes — happy to submit a PR for Option A (thread turn_id through the dispatch chain) if the maintainers agree this is the right approach.
[BUG] post_tool_call hook does not pass
turn_id— turn-counting plugins silently no-opBug Description
The
post_tool_callplugin hook invocation inmodel_tools.pydoes not includeturn_idin its kwargs. Plugins that rely onturn_idfor per-turn deduplication or turn counting silently receive an empty string, causing them to under-count turns and never trigger their logic.Environment
Root Cause
In
model_tools.py:996-1004, theinvoke_hook("post_tool_call", ...)call passes:turn_idis not passed. Furthermore,handle_function_call()(line 802) does not even accept aturn_idparameter — it is not available anywhere in the dispatch chain.The
_DEFAULT_PAYLOADSinhermes_cli/hooks.py:120-128confirms this: thepost_tool_calltest payload containstool_name,args,session_id,task_id,tool_call_id,result,duration_ms— but noturn_id.Impact
Any plugin that uses
turn_idfor turn deduplication or counting will silently malfunction. Example — our ASH (Auto Session Handoff) plugin:Since
turn_idis always"", the counter never increments,turn_countstays at 0, and the plugin's auto-trigger logic is dead code. The plugin appears loaded and registered (logs show "ASH v1.0.0 loaded"), but never fires its core function. This is a silent failure — no error, no warning, no log entry.Log Evidence
The plugin loads successfully and
on_session_starthook fires correctly, butpost_tool_callnever triggers a handoff across 27 invocations over 12 hours — becauseturn_idis always empty.Related Issues
session_idnot propagated toget_pre_tool_call_block_messagefrom its 3 callsites —pre_tool_callplugin hooks that scope by session silently no-op #34618 —session_idnot propagated topre_tool_call(same pattern: hook parameter missing from dispatch → silent no-op)session_id(same pattern)pre_tool_callhooks receive emptysession_idThese confirm a systemic pattern of hook parameters being silently dropped in the dispatch chain.
Proposed Fix
Option A (minimal): Add
turn_idto theinvoke_hookcall inmodel_tools.py. Requires threadingturn_idfromrun_agent.pythroughhandle_function_call().Option B (no upstream change needed): Plugins that need turn counting should query
state.dbdirectly (e.g.,SELECT COUNT(*) FROM messages WHERE role='user' AND session_id=?) rather than relying onturn_idfrom the hook payload. This is the workaround we applied locally.Willing to Submit PR
Yes — happy to submit a PR for Option A (thread
turn_idthrough the dispatch chain) if the maintainers agree this is the right approach.