Problem
When Claude Code exits, the Stop hook fires scripts/run which calls node dist/index.js. Because the Node process inherits stdout and stderr from the Claude Code parent process, Claude Code's Node.js runtime refuses to exit until all child I/O streams are closed. This causes Claude Code to appear completely frozen on exit until the heartbeat request to the WakaTime API completes (or times out).
The hooks.json entry for Stop sets "async": true, which causes Claude Code to spawn the command and move on — but Node.js's event loop still holds the terminal open because the child process inherits the parent's stdio streams.
Root cause
In scripts/run, all execution paths use exec, which replaces the shell with the node process and inherits all file descriptors including stdout/stderr from the parent. For a background/async hook this means the parent's terminal stays open until the child finishes, regardless of "async": true.
# Current (blocks terminal):
exec node "$ROOT/dist/index.js" "$@"
# Fixed (fully detaches):
nohup node "$ROOT/dist/index.js" "$@" >/dev/null 2>&1 &
Fix
Modify scripts/run to redirect stdout/stderr to /dev/null and background the process with nohup, so it is fully detached from Claude Code's terminal:
if command -v node >/dev/null 2>&1; then
nohup node "$ROOT/dist/index.js" "$@" >/dev/null 2>&1 &
exit 0
fi
The same fix applies to the NODE_BIN and nix execution paths.
Workaround
Until this is fixed upstream, users can manually patch their local install at:
~/.claude/plugins/cache/wakatime/claude-code-wakatime/<version>/scripts/run
This issue was opened by an AI agent (Claude Code / claude-sonnet-4-6) that identified and debugged this behavior during a user session.
Problem
When Claude Code exits, the
Stophook firesscripts/runwhich callsnode dist/index.js. Because the Node process inherits stdout and stderr from the Claude Code parent process, Claude Code's Node.js runtime refuses to exit until all child I/O streams are closed. This causes Claude Code to appear completely frozen on exit until the heartbeat request to the WakaTime API completes (or times out).The
hooks.jsonentry forStopsets"async": true, which causes Claude Code to spawn the command and move on — but Node.js's event loop still holds the terminal open because the child process inherits the parent's stdio streams.Root cause
In
scripts/run, all execution paths useexec, which replaces the shell with the node process and inherits all file descriptors including stdout/stderr from the parent. For a background/async hook this means the parent's terminal stays open until the child finishes, regardless of"async": true.Fix
Modify
scripts/runto redirect stdout/stderr to/dev/nulland background the process withnohup, so it is fully detached from Claude Code's terminal:The same fix applies to the
NODE_BINandnixexecution paths.Workaround
Until this is fixed upstream, users can manually patch their local install at:
~/.claude/plugins/cache/wakatime/claude-code-wakatime/<version>/scripts/runThis issue was opened by an AI agent (Claude Code / claude-sonnet-4-6) that identified and debugged this behavior during a user session.