Debug Hook Artifacts
All Claude Code hooks in this project write structured JSONL artifacts to .tx/hook-artifacts/<run-id>.jsonl. Each line is a JSON object representing one hook invocation.
Quick Start
List all runs with artifact data
ls -lhtr .tx/hook-artifacts/*.jsonl 2>/dev/null
View all hook events for a specific run
If $ARGUMENTS is provided, use it as the run-id. Otherwise, use the most recent JSONL file.
# Pretty-print all events
cat .tx/hook-artifacts/$ARGUMENTS.jsonl | jq .
# If no run-id given, use the latest file
ls -t .tx/hook-artifacts/*.jsonl | head -1 | xargs cat | jq .
Filter by hook type
# Only safety decisions
grep "pre-safety" .tx/hook-artifacts/$ARGUMENTS.jsonl | jq .
# Only stop-hook results (did ralph get blocked?)
grep "stop-ensure-completion" .tx/hook-artifacts/$ARGUMENTS.jsonl | jq .
# Only lint check results
grep "post-lint-check" .tx/hook-artifacts/$ARGUMENTS.jsonl | jq .
# Only failure recovery events
grep "post-bash-recovery" .tx/hook-artifacts/$ARGUMENTS.jsonl | jq .
Summary statistics
# Count events per hook type
cat .tx/hook-artifacts/$ARGUMENTS.jsonl | jq -r '._meta.hook' | sort | uniq -c | sort -rn
# Show all deny/block decisions
cat .tx/hook-artifacts/$ARGUMENTS.jsonl | jq 'select(._meta.decision == "deny" or .passed == false)'
# Timeline of events
cat .tx/hook-artifacts/$ARGUMENTS.jsonl | jq -r '"\(._meta.timestamp) \(._meta.hook) \(._meta.decision // .passed // "info")"'
Artifact Schema
Each JSONL line has a _meta object plus hook-specific fields:
{
"_meta": {
"hook": "pre-safety", // Hook name
"timestamp": "2026-02-05T...", // UTC ISO 8601
"tool": "Bash", // (PreToolUse only) Which tool triggered
"decision": "deny|allow|warn", // (PreToolUse only) Safety decision
"exit_code": 1, // (PostToolUse only) Command exit code
"failure_type": "test", // (recovery only) test|lint|build
"ralph_mode": "true", // (stop only) Whether RALPH was active
"task_id": "tx-abc123", // (prompt-context only) Task being discussed
"source": "search" // (prompt-context only) How learnings were found
},
"reason": "...", // Why blocked/warned
"failures": "...", // Stop hook failure codes
"passed": true, // Stop hook pass/fail
"context": "...", // Injected context content
"command": "...", // Command that triggered the hook
"learnings": "..." // Injected learnings content
}
Hook Inventory
| Hook | Event | Fires When |
|---|---|---|
pre-safety |
PreToolUse | Every Bash/Write/Edit call — checks for dangerous ops |
pre-validate-paths |
PreToolUse | Every Write/Edit/Read — blocks paths outside project |
post-bash |
PostToolUse | Every Bash call — tracks test status, dispatches recovery |
post-bash-recovery |
PostToolUse | Bash failures — parses test/lint/build errors |
post-lint-check |
PostToolUse | Every Write/Edit on .ts files — runs ESLint |
session-start-context |
SessionStart | Session begins — loads task context + learnings |
prompt-context |
UserPromptSubmit | Every prompt — searches for relevant learnings |
stop-ensure-completion |
Stop | Agent tries to stop — checks task done, tests, coverage, uncommitted changes |
pre-compact |
PreCompact | Auto-compact — archives transcript + learnings |
Common Debugging Scenarios
"Why did ralph get blocked from stopping?"
grep "stop-ensure-completion" .tx/hook-artifacts/<run-id>.jsonl | jq .
Look at the failures field — it lists codes like TASK_NOT_DONE, TESTS_FAILED, UNCOMMITTED_CHANGES, COVERAGE_LOW.
"What safety decisions were made?"
grep "pre-safety" .tx/hook-artifacts/<run-id>.jsonl | jq '._meta.decision'
"What context did the agent receive?"
grep -E "(session-start|prompt-context)" .tx/hook-artifacts/<run-id>.jsonl | jq .context
"What lint errors were found?"
grep "post-lint-check" .tx/hook-artifacts/<run-id>.jsonl | jq '{file: ._meta.file, errors: ._meta.errors, warnings: ._meta.warnings}'
Converted and distributed by TomeVault — claim your Tome and manage your conversions.