Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b043bb1
docs(topics): record measured PostToolUse hook audit and logging-pipe…
claude Sep 4, 2026
4ff800f
docs(topics): commit the measurement harness and correct the guard-fi…
claude Sep 4, 2026
05cd832
docs(topics): lock interview rounds 2 and 3 into the logging-pipeline…
claude Sep 4, 2026
0396b7a
docs(topics): complete the logging-pipeline brief through interview r…
claude Sep 4, 2026
282a0e5
docs(topics): pin the kill-switch hoist site count to a precise grep
claude Sep 4, 2026
d773784
docs(topics): record claude-ops version coordination with the sibling…
claude Sep 4, 2026
29815e4
docs(topics): lock the confirmed work allocation into the logging-pip…
claude Sep 5, 2026
7f1a9ed
docs(topics): draft the hook-logging-pipeline plan and design early-exit
claude Sep 5, 2026
b889b97
Merge remote-tracking branch 'origin/main' into claude/posttool-hooks…
claude Sep 5, 2026
715e261
docs(topics): fold the two stress-test passes into the hook-logging-p…
claude Sep 5, 2026
969ebe8
docs(topics): record the 2026-09-05 re-measurement of the verifier lane
claude Sep 5, 2026
fb7602a
perf(guardrails): batch the skill-reference index and answer cli-flag…
claude Sep 5, 2026
0c99789
docs: record the after-fix figures for the verifier lane
claude Sep 5, 2026
0a1237c
fix(guardrails): one shared definition for the cli-flag cache, exact …
claude Sep 5, 2026
6d95ec4
docs(topics): record PR #3747 against Phase 1
claude Sep 5, 2026
8313c16
perf(hooks): PostToolUse kill-switch hoist and gate widening
claude Sep 5, 2026
b489dfe
fix(instruction-placement): read the kill switch before the dirname f…
claude Sep 5, 2026
11f15f8
fix(guardrails): review fixes for the verifier lane and the two lint …
claude Sep 5, 2026
caf1dbe
feat(claude-ops): per-session hook event log producer, sink routing, …
claude Sep 5, 2026
7083127
feat(claude-ops): six userConfig keys for the per-session log and its…
claude Sep 5, 2026
5b67da8
feat(claude-ops): generated hook event registry, producer rows, Sessi…
claude Sep 5, 2026
ee0e33b
test(claude-ops): keep the held-open timing local to its subshell; ad…
claude Sep 5, 2026
d5d2cc8
docs(topics): record the per-session pipeline measurements
claude Sep 5, 2026
6ff9efb
fix(claude-ops): heal an empty log-root guard instead of refusing the…
claude Sep 5, 2026
4dc6bc8
feat(claude-ops): setup apply for the hook log guard and the hook-eve…
claude Sep 5, 2026
7d6d2f4
docs(claude-ops): move the retirement record's version above the hois…
claude Sep 5, 2026
37606c9
feat(claude-ops): read the hook log root in the observability skill
claude Sep 5, 2026
b1d3d6d
Merge origin/main into claude/hook-logging-pipeline-ji6rl5
claude Sep 5, 2026
0d1273e
docs(claude-ops): changelog, README and convention notes for the hook…
claude Sep 5, 2026
bbe7253
docs(topics): prune the hook-logging-pipeline contract slice before t…
claude Sep 5, 2026
8b64c52
fix(claude-ops): read to the bound past a nested brace, and basename …
claude Sep 5, 2026
0af5645
fix(claude-ops): count braces through variables, and contain the log …
claude Sep 5, 2026
87700eb
chore(claude-ops): regenerate the catalog and lint the registry fixture
claude Sep 5, 2026
5c53821
Merge origin/main into claude/hook-logging-pipeline-ji6rl5
claude Sep 5, 2026
74238b2
docs: regenerate the skill cheat sheet for the observability summary
claude Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 57 additions & 10 deletions .claude/hooks/hook-telemetry-sink.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
#!/usr/bin/env bash
# Reference telemetry sink for claude-ops. Maps a hook-telemetry envelope (from
# ANY producer, per docs/conventions/hook-telemetry) into one JSONL line in
# .claude/observability/hook-events.jsonl — the shape the claude-observability
# skill reads ({ts, event, hook, tool, duration_ms, exit_code, subject, status}).
# ANY producer, per docs/conventions/hook-telemetry) into one JSONL line under
# the log root (.observability/claude by default, project-relative; the
# session_event_log_dir option moves it).
#
# Two routes, decided by the envelope's `data.session_id`:
# * present and well-formed: one spine-shaped line appended to
# sessions/<session_id>.jsonl, beside the per-session event log
# (session-event-log.sh); `source: "envelope"` tells the reader which
# producer wrote it. No lock: one file per session removes the shared
# write.
# * absent: the legacy shape ({ts, event, hook, tool, duration_ms, exit_code,
# subject, status}) appended to hook-events.jsonl under the same root, the
# shared file the observability skill has always read, under its lock.
#
# Field mapping: ts<-timestamp, event<-hook_event, hook<-hook, tool<-data.tool,
# subject<-data.subject; status translates (ok->success) and exit_code derives
# from status (error/blocked->2, else 0), since the skill keys errors on it.
# subject<-data.subject, changed<-data.changed (when a producer sends one);
# status translates (ok->success) and exit_code derives from status
# (error/blocked->2, else 0), since the skill keys errors on it.
#
# The root carries a self-ignoring .gitignore inside a checkout, healed on the
# first write when absent (session-log-lib.sh); a guard an operator changed is
# respected and the write refused.
#
# Wire it by pointing HOOK_TELEMETRY_SINK at this script (relative path committed
# in settings.json is the portable, team-shared form):
Expand All @@ -27,6 +42,8 @@ set -uo pipefail
# the sibling pr-linkage-mcp-gate.sh reaching shared sources by relative path).
# shellcheck source=../../lib/hook-utils.sh
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/hook-utils.sh"
# shellcheck source=../../plugins/claude-ops/hooks/session-log-lib.sh
source "$(dirname "${BASH_SOURCE[0]}")/../../plugins/claude-ops/hooks/session-log-lib.sh"

INPUT=$(cat)
[[ -n "$INPUT" ]] || exit 0
Expand All @@ -42,9 +59,11 @@ command -v jq >/dev/null 2>&1 || exit 0
mapfile -t FIELDS < <(printf '%s' "$INPUT" | jq -r '
if (.hook and .hook_event and (.duration_ms != null) and .status)
then (.timestamp // ""), .hook_event, .hook, (.data.tool // ""),
(.duration_ms | tostring), (.data.subject // ""), .status
(.duration_ms | tostring), (.data.subject // ""), .status,
(.data.session_id // "" | tostring),
(.data.changed | if . == true then "true" elif . == false then "false" else "" end)
else empty end' 2>/dev/null | tr -d '\r')
[[ "${#FIELDS[@]}" -eq 7 ]] || exit 0
[[ "${#FIELDS[@]}" -eq 9 ]] || exit 0

TS="${FIELDS[0]}"
EVENT="${FIELDS[1]}"
Expand All @@ -53,6 +72,8 @@ TOOL="${FIELDS[3]}"
DURATION_MS="${FIELDS[4]}"
SUBJECT="${FIELDS[5]}"
STATUS="${FIELDS[6]}"
SESSION_ID="${FIELDS[7]}"
CHANGED="${FIELDS[8]}"

# Translate the envelope status to the record shape the skill reads. Known
# values pass through (ok → success); an unrecognized value is treated as a
Expand Down Expand Up @@ -85,8 +106,34 @@ blocked)
esac

project_dir=$(hook::repo_root "${CLAUDE_PROJECT_DIR:-.}")
log_dir="${project_dir%/}/.claude/observability"
mkdir -p "$log_dir" 2>/dev/null || exit 0
root=""
slog_root_to root "$project_dir"
[[ -n "$root" ]] || exit 0
slog_guard_ok "$root" "$project_dir" || exit 0

if [[ -n "$SESSION_ID" ]] && slog_valid_id "$SESSION_ID"; then
[[ -d "$root/sessions" ]] || mkdir -p "$root/sessions" 2>/dev/null || exit 0
LINE=$(MSYS_NO_PATHCONV=1 jq -nc \
--arg ts "$TS" \
--arg session_id "$SESSION_ID" \
--arg event "$EVENT" \
--arg hook "$HOOK" \
--arg tool "$TOOL" \
--argjson duration_ms "${DURATION_MS:-0}" \
--argjson exit_code "${EXIT_CODE:-0}" \
--arg subject "$SUBJECT" \
--arg status "$STATUS_OUT" \
--arg changed "$CHANGED" \
'{ts: $ts, session_id: $session_id, hook_event_name: $event, status: $status,
duration_ms: $duration_ms, source: "envelope", hook: $hook,
exit_code: $exit_code, subject: $subject, tool: $tool}
+ (if $changed == "" then {} else {changed: ($changed == "true")} end)' 2>/dev/null) || exit 0
[[ -n "$LINE" ]] || exit 0
printf '%s\n' "$LINE" >>"$root/sessions/$SESSION_ID.jsonl" 2>/dev/null
exit 0
fi

mkdir -p "$root" 2>/dev/null || exit 0

LINE=$(MSYS_NO_PATHCONV=1 jq -nc \
--arg ts "$TS" \
Expand All @@ -102,6 +149,6 @@ LINE=$(MSYS_NO_PATHCONV=1 jq -nc \
subject: $subject, status: $status}' 2>/dev/null) || exit 0
[[ -n "$LINE" ]] || exit 0

hook::append_jsonl "${log_dir}/hook-events.jsonl" "$LINE"
hook::append_jsonl "${root}/hook-events.jsonl" "$LINE"

exit 0
16 changes: 11 additions & 5 deletions .claude/hooks/hook-telemetry-sink.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ repo_root="$(cd "$here/../.." && pwd)"
local_copy="$here/hook-telemetry-sink.sh"
upstream="$repo_root/plugins/claude-ops/hooks/hook-telemetry-sink.sh"

[[ -f "$local_copy" ]] || { echo "FAIL: missing $local_copy"; exit 1; }
[[ -f "$upstream" ]] || { echo "FAIL: missing $upstream"; exit 1; }
[[ -f "$local_copy" ]] || {
echo "FAIL: missing $local_copy"
exit 1
}
[[ -f "$upstream" ]] || {
echo "FAIL: missing $upstream"
exit 1
}

# Strip the resolution block from both: the shellcheck source directive, the
# source line itself, and the repo-copy-only comment lines that explain it.
Expand Down Expand Up @@ -50,9 +56,9 @@ fi

smoke_dir="$(mktemp -d)"
trap 'rm -f "$norm_upstream" "$norm_local"; rm -rf "$smoke_dir"' EXIT
printf '%s' '{"schema":"hook-telemetry/v1","hook":"drift-test-smoke","hook_event":"PreToolUse","duration_ms":1,"status":"ok","data":{"tool":"Bash","subject":"smoke"}}' \
| CLAUDE_PROJECT_DIR="$smoke_dir" bash "$local_copy"
if [[ -s "$smoke_dir/.claude/observability/hook-events.jsonl" ]]; then
printf '%s' '{"schema":"hook-telemetry/v1","hook":"drift-test-smoke","hook_event":"PreToolUse","duration_ms":1,"status":"ok","data":{"tool":"Bash","subject":"smoke"}}' |
CLAUDE_PROJECT_DIR="$smoke_dir" bash "$local_copy"
if [[ -s "$smoke_dir/.observability/claude/hook-events.jsonl" ]]; then
echo "PASS: sink executes and appends a record (source target resolves)"
else
echo "FAIL: sink ran but appended no record — source target or mapping broken"
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Thumbs.db
# Local-only Claude Code state
.claude/settings.local.json
.claude/observability/
.observability/
.claude/worktrees/
# loop-lane escalation records (docs/conventions/loop-lane/README.md §2 — signal, not storage)
.claude/lane-escalations/
Expand Down
Loading
Loading