Hook in observability
tool-call-logger
A PreToolUse/PostToolUse hook that appends one JSON line per tool call (tool name, inputs, and response) to a local log file, with secret redaction and payload truncation. Use to audit, debug, or observe exactly what Claude Code did during a session.
Install
As a plugin
Bundled in the tool-call-logger plugin. Add the marketplace once, then install the bundle; it updates with the marketplace.
/plugin marketplace add KhaledSaeed18/dotclaude/plugin install tool-call-logger@dotclaudeAs a single item
Run from the project root; the hook lands under .claude/. Copy it to ~/.claude/ to make it global.
npx shadcn@latest add KhaledSaeed18/dotclaude/tool-call-loggerInstalled as a single item, a hook still needs the settings block documented below added to .claude/settings.json. The plugin route wires it automatically.
Files installed
.claude/hooks/tool-call-logger/HOOK.md.claude/hooks/tool-call-logger/log-tool-calls.mjs
What it does
tool-call-logger
A Claude Code hook that records every tool call to a JSONL file so you can audit, debug, or analyse what happened in a session. It runs as a command hook: Claude Code pipes the tool event to the script on stdin, and the script appends one sanitized line per call.
- Non-blocking: swallows every error and always exits
0, so it can never interrupt or fail a tool call. - Zero dependencies: Node standard library only (
node >= 18). - Safe by default: redacts secret-looking keys (
token,password,apiKey,authorization, …) and truncates oversized fields.
Files
| File | Purpose |
|---|---|
log-tool-calls.mjs |
The hook script. Reads the event from stdin, appends a record. |
HOOK.md |
This file. |
After npx shadcn@latest add KhaledSaeed18/dotclaude/tool-call-logger, both land in .claude/hooks/tool-call-logger/.
Activate it (required manual step)
The installer copies the files but cannot edit your settings.json, hooks are configuration, not loadable files, so you wire it up once by hand. Add this to .claude/settings.json (project), or ~/.claude/settings.json (global):
{
"hooks": {
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/tool-call-logger/log-tool-calls.mjs\""
}
]
}
]
}
}
matcher: "*"logs every tool. Narrow it to specific tools with a regex, e.g."Edit|Write|Bash".$CLAUDE_PROJECT_DIRis set by Claude Code to the project root, so the path resolves no matter the working directory. If you installed the script globally under~/.claude/, point the command there instead.PostToolUsecaptures the tool input and its response. To log the intent before a tool runs (no response yet), add the same block underPreToolUse: the script handles both events. Wiring both gives you a before/after trail.
Run /hooks in Claude Code (or restart the session) to load the change. For safety, Claude Code captures a snapshot of hooks at startup, so external edits aren't applied mid-session until you review them via /hooks.
Output
Records are appended as JSON Lines (one object per line) to:
$CLAUDE_PROJECT_DIR/.claude/logs/tool-calls.jsonl
Each line:
{
"ts": "2026-06-08T12:34:56.789Z",
"event": "PostToolUse",
"session_id": "abc123",
"cwd": "/path/to/project",
"tool_name": "Bash",
"tool_input": { "command": "git status", "description": "Show working tree status" },
"tool_response": { "stdout": "…", "stderr": "", "interrupted": false }
}
Read it back with standard tools, e.g. the last 20 Bash calls:
grep '"tool_name":"Bash"' .claude/logs/tool-calls.jsonl | tail -20 | jq .
Configuration
| Env var | Default | Purpose |
|---|---|---|
CLAUDE_TOOL_LOG |
$CLAUDE_PROJECT_DIR/.claude/logs/tool-calls.jsonl |
Override the log path (absolute, or relative to cwd). |
CLAUDE_TOOL_LOG_MAXLEN |
2000 |
Max characters per string field before truncation. |
Set them in the hook command if you want, e.g.:
"command": "CLAUDE_TOOL_LOG=/tmp/claude-tools.jsonl node \"$CLAUDE_PROJECT_DIR/.claude/hooks/tool-call-logger/log-tool-calls.mjs\""
Keep logs out of Git
The log can grow quickly and may contain repo paths or snippets. Add it to .gitignore:
.claude/logs/
Troubleshooting
- No file appears. Confirm the hook is registered (
/hooks), thatnodeis on PATH, and that thecommandpath is correct. The script fails silently by design, so test it directly:echo '{"hook_event_name":"PostToolUse","tool_name":"Bash","tool_input":{"command":"ls"},"tool_response":{"stdout":"ok"}}' \ | node .claude/hooks/tool-call-logger/log-tool-calls.mjs cat .claude/logs/tool-calls.jsonl - A value shows
[redacted]. That key matched the secret pattern, expected. Adjust theSECRET_KEYregex in the script if it's over-eager for your data. - Lines are cut with
…(+N chars). RaiseCLAUDE_TOOL_LOG_MAXLEN.
Hooks run arbitrary commands on your machine with your credentials whenever their event fires. Read any hook script (including this one) before enabling it, and only register hooks you trust.
This is the hook as installed, rendered from its source on GitHub. Read it as Markdown.