You are the hooks-status skill — reader for .claude/runtime/audit/hooks.jsonl. Closes backlog 1.2 (consume hooks.jsonl) + 1.7 (hook usage status) as one skill.
What this skill does
Lintel ships 33 hooks, 9 of them auto-registered on a plugin install. Hooks.jsonl is written on override attempts + trigger events, but nothing reads it. This skill:
- Aggregates trigger counts per hook (last N days)
- Identifies override patterns ("customer-data-block overridden 6× this week" → flag-worthy)
- Surfaces dead hooks (zero triggers > 30 days — candidate for cleanup)
- Correlates override trends with skills (via run_id cross-reference to usage-log)
Closes the self-observation-spine loop per Cohort 2 goal: data written → data read → operator sees pattern.
When to use
- "Which hooks actually fire?" →
/li:hooks-status --triggers --days 30 - "Have I overridden too much?" →
/li:hooks-status --overrides - "Which hooks are dead?" →
/li:hooks-status --dead --days 90(no trigger past 90 days) - Maintenance pre-flight: combine with
/li:usage-log --reportfor a full observation pass
When NOT to use
- Real-time hook-firing detection — this is retroactive, reads the jsonl after the event
- Hook design or install —
bin/li-doctorhas a hook-install-state check
Workflow
Step 1 — Read hooks.jsonl
HOOKS_LOG=".claude/runtime/audit/hooks.jsonl"
if [ ! -f "$HOOKS_LOG" ]; then
echo "No hooks.jsonl yet. Hooks log at first override or trigger event."
exit 0
fi
Step 2 — Aggregate per requested view
--triggers --days N:
- Parse jsonl, filter by
tswithin last N days - Group by
hook_name, count triggers - Surface table: hook_name | trigger_count | last_trigger_ts
--overrides:
- Filter lines with
override: true - Group by
hook_name+override_reason - Surface: "hook X overridden N× (reasons: ...)"
- Flag if N > 5 for any hook in past 7 days (signals friction)
--dead --days N:
- For each registered hook (from
hooks/-dir scan), check last trigger - If no trigger past N days → flag as candidate cleanup
- Surface: hook_name | days_since_last_trigger | install_state
Step 3 — Cross-reference with usage-log (optional via --correlate)
If --correlate flag:
- Read
usage-*.jsonl(usage-log writer output) - Match override events to invoked skills via
run_id - Surface: "when hook X overrides, skill Y is invoked N% of the time" → causal-link hint
Step 4 — Render report
Markdown table output to stdout. Operator pipes to less or redirects to a file.
Status protocol
- DONE — report rendered
- DONE_WITH_CONCERNS — report rendered but hooks.jsonl is malformed on some lines (skip + count in report)
- BLOCKED —
.claude/runtime/audit/hooks.jsonlpermissions deny read - NEEDS_CONTEXT — invocation without a view flag (
--triggers/--overrides/--dead)
Integration
Reads:
.claude/runtime/audit/hooks.jsonl(canonical hooks-audit log)~/.lintel/audit/usage-*.jsonl(optional cross-reference via--correlate)hooks/-dir scan (for dead-hook detection — which hooks are installed)
Writes:
- stdout (markdown report)
Consumed by:
- Operator (solo-invocation pattern)
/li:maintenance(5.3 — feed observation-data)
Anti-patterns
- Modifying hooks.jsonl — this is reader-only. The audit log is immutable.
- Streaming live triggers — this processes batches. Real-time hook-watching is an
inotify/fswatchjob, not this.
Failure recovery
- Malformed JSON lines: skip + count + report at end ("3 malformed lines skipped").
- Empty hooks.jsonl: surface "No hook events recorded yet."
- usage-log not present for
--correlate: warn + proceed without correlation.
Recommended next steps after invocation
- For the full picture: pair with
/li:usage-log --report(sibling skill) - On an override spike: surface to operator decision on whether hook-tuning is needed
- On a dead hook:
/li:doctor --hooksvalidates installed-state before a cleanup decision