Skip to content

[Bug]: [Windows] hook_install.exe adds ~5s to every Claude Code tool call; 276 PreToolUse timeouts over 7 days #46

Description

@laxer84

Summary

The Claude Code integration registers hook_install.exe on 9 hook events, including PreToolUse and PostToolUse. Those two fire on every tool call and block the agent loop until they return.

Each invocation costs a fixed ~4.9 s, and in real sessions it regularly exceeds Claude Code's 10 s hook timeout. Across 50 sessions (~7 days) on this machine I measured 276 PreToolUse timeouts plus 8 UserPromptSubmit timeouts — roughly 50 minutes of blocked wall-clock time.

Environment

AhaKey / Vibecoding Keyboard 1.3.0 (KeyboardConfig.exe FileVersion 1.3.0)
hook_install.exe 10.4 MB, built 2026-05-15, PyInstaller onefile (frozen=True per its own log; no version metadata embedded)
OS Windows 11 Pro, build 10.0.26200
Claude Code 2.1.220 (native install)
BLE bridge BLE_tcp_driver.exe running, listening on 0.0.0.0:9000

The bridge is up and accepting connections throughout — this is not a "server not running" case.

Measurements

1. Fixed ~4.9 s per invocation, independent of payload

Feeding a valid PreToolUse payload on stdin and timing to process exit:

empty transcript_path         4894 ms   exit=0
small transcript (97 B)       5098 ms   exit=0
median transcript (758 KB)    4575 ms   exit=0
large transcript (43.7 MB)    4919 ms   exit=0
large transcript, repeat      5080 ms   exit=0

Payload size makes no difference, so the cost is startup, not work.

2. Consistent across sequential runs

4694 / 4666 / 5121 / 5149 / 4824 / 4748 ms   (all exit=0)

3. Concurrency is not the bottleneck

Four parallel invocations completed in 5.9 s wall clock (~5.7 s each), so they are not serializing on the TCP bridge.

4. In real sessions it crosses the 10 s timeout constantly

From Claude Code transcripts across 50 sessions / 6.9 days — hooks killed at timeoutMs: 10000:

Hook event Timeouts Median duration
PreToolUse:Bash 123 10,646 ms
PreToolUse:Read 55 10,679 ms
PreToolUse:Edit 40 10,609 ms
PreToolUse:Grep 16 10,690 ms
PreToolUse:Agent 12 10,598 ms
PreToolUse: (Write/Glob/TodoWrite/Skill/AskUserQuestion/ToolSearch/WebFetch) 30 ~10,700 ms
UserPromptSubmit 8 10,699 ms

PreToolUse and UserPromptSubmit are attributable to hook_install.exe alone — no other hook is registered on those events on this machine.

I can't fully account for the gap between the 4.9 s standalone baseline and the >10 s in-session figure. One hypothesis worth profiling: PyInstaller onefile re-extracts the 10.4 MB bundle to %TEMP%\_MEIxxxxx on every run, and under a busy session (heavy concurrent disk I/O, plus Defender re-scanning each fresh extraction) that tips past 10 s.

5. The debug log grows unbounded and records nothing diagnostic

C:\Vibecoding Keyboard\ahakey-hook-debug.log is 21.7 MB / 119,433 lines, with no rotation or size cap. Every line is a start record — there is no completion, duration, or error line anywhere, so the log cannot be used to diagnose the hang:

[2026-08-01 11:21:52] [launcher] pid=45564 start frozen=True argv=['C:\Vibecoding Keyboard\hook_install.exe', 'PreToolUse'] executable='C:\Vibecoding Keyboard\hook_install.exe'

Line-type breakdown over a 200k-line sample — all start, zero completions:

58395  PreToolUse
56990  PostToolUse
 1306  UserPromptSubmit
 1291  Stop
  529  SessionStart
  463  SessionEnd
  441  PermissionRequest

Impact

Because PreToolUse blocks the agent loop, this adds ~5 s (frequently the full 10 s timeout) before every Bash / Read / Edit / Grep / Glob / Write call. An agent session making hundreds of tool calls pays it every time.

Suggested fixes

  1. Don't register on PreToolUse / PostToolUse unless the payload is genuinely needed. These are the only per-tool-call blocking events. If the goal is "react when the agent stops or needs input", Stop / Notification / PermissionRequest alone cost nothing per tool call.
  2. Mark the hooks "async": true where the result isn't consumed — Claude Code supports it and it removes the blocking cost entirely.
  3. Replace the per-invocation frozen-exe launch with a persistent client. The BLE bridge is already a long-lived local server on :9000; a small native sender (or reusing the already-running process) would cut ~4.9 s to milliseconds.
  4. If the onefile build must stay, ship --onedir instead. That avoids re-extracting the bundle to %TEMP% on every invocation, which is the most likely source of the fixed cost.
  5. Rotate/cap the debug log, and log completions + durations, not just starts.

Workaround for other users

Remove the PreToolUse and PostToolUse entries from the hooks block in ~/.claude/settings.json, keeping only the low-frequency events (SessionStart, SessionEnd, Stop, Notification, PermissionRequest). That eliminates the per-tool-call cost while keeping the integration working.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions