Skip to content

[BUG] post_tool_call hook does not pass turn_id — turn-counting plugins silently no-op #60578

Description

@tobiglevent001

[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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low — cosmetic, nice to havecomp/agentCore agent runtime: loop, agent_init, prompt builder, context-compression, responses endpointcomp/pluginsPlugin system and bundled pluginssweeper:implemented-on-mainSweeper: behavior already present on current maintype/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions