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
- 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.
- Mark the hooks
"async": true where the result isn't consumed — Claude Code supports it and it removes the blocking cost entirely.
- 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.
- 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.
- 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.
Summary
The Claude Code integration registers
hook_install.exeon 9 hook events, includingPreToolUseandPostToolUse. 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
PreToolUsetimeouts plus 8UserPromptSubmittimeouts — roughly 50 minutes of blocked wall-clock time.Environment
KeyboardConfig.exeFileVersion 1.3.0)hook_install.exefrozen=Trueper its own log; no version metadata embedded)BLE_tcp_driver.exerunning, listening on0.0.0.0:9000The 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
PreToolUsepayload on stdin and timing to process exit:Payload size makes no difference, so the cost is startup, not work.
2. Consistent across sequential runs
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:PreToolUse:BashPreToolUse:ReadPreToolUse:EditPreToolUse:GrepPreToolUse:AgentPreToolUse:(Write/Glob/TodoWrite/Skill/AskUserQuestion/ToolSearch/WebFetch)UserPromptSubmitPreToolUseandUserPromptSubmitare attributable tohook_install.exealone — 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%\_MEIxxxxxon 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.logis 21.7 MB / 119,433 lines, with no rotation or size cap. Every line is astartrecord — there is no completion, duration, or error line anywhere, so the log cannot be used to diagnose the hang:Line-type breakdown over a 200k-line sample — all
start, zero completions:Impact
Because
PreToolUseblocks 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
PreToolUse/PostToolUseunless 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/PermissionRequestalone cost nothing per tool call."async": truewhere the result isn't consumed — Claude Code supports it and it removes the blocking cost entirely.:9000; a small native sender (or reusing the already-running process) would cut ~4.9 s to milliseconds.--onedirinstead. That avoids re-extracting the bundle to%TEMP%on every invocation, which is the most likely source of the fixed cost.Workaround for other users
Remove the
PreToolUseandPostToolUseentries from thehooksblock 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.