From cb4e4ca711c6e77c35d654d2d62bc56b6a344c81 Mon Sep 17 00:00:00 2001 From: tsli Date: Tue, 17 Feb 2026 10:18:36 +0800 Subject: [PATCH] fix: use CLI argument for hook phase detection in observe.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The observe.sh script receives "pre" or "post" as $1 from the hook config, but the Python code was looking for a "hook_type" field in the stdin JSON. Claude Code does NOT include "hook_type" in the JSON payload passed to hooks, so it always defaulted to "unknown", causing all observations to be recorded as "tool_complete" — PreToolUse events were never distinguished from PostToolUse. Fix: capture $1 as HOOK_PHASE and pass it to Python via env var. This also fixes TIMESTAMP export in the .cursor copy where inline `VAR=val cmd` syntax didn't propagate to the python subprocess. --- .../continuous-learning-v2/hooks/observe.sh | 23 +++++++++++++------ .../continuous-learning-v2/hooks/observe.sh | 17 ++++++++++---- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/.cursor/skills/continuous-learning-v2/hooks/observe.sh b/.cursor/skills/continuous-learning-v2/hooks/observe.sh index 98b6b9d2..05b04c14 100755 --- a/.cursor/skills/continuous-learning-v2/hooks/observe.sh +++ b/.cursor/skills/continuous-learning-v2/hooks/observe.sh @@ -36,6 +36,9 @@ set -e +# Hook phase from CLI argument: "pre" (PreToolUse) or "post" (PostToolUse) +HOOK_PHASE="${1:-post}" + CONFIG_DIR="${HOME}/.claude/homunculus" OBSERVATIONS_FILE="${CONFIG_DIR}/observations.jsonl" MAX_FILE_SIZE_MB=10 @@ -57,15 +60,22 @@ if [ -z "$INPUT_JSON" ]; then fi # Parse using python via stdin pipe (safe for all JSON payloads) -PARSED=$(echo "$INPUT_JSON" | python3 -c ' +# Pass HOOK_PHASE via env var since Claude Code does not include hook type in stdin JSON +PARSED=$(echo "$INPUT_JSON" | HOOK_PHASE="$HOOK_PHASE" python3 -c ' import json import sys +import os try: data = json.load(sys.stdin) + # Determine event type from CLI argument passed via env var. + # Claude Code does NOT include a "hook_type" field in the stdin JSON, + # so we rely on the shell argument ("pre" or "post") instead. + hook_phase = os.environ.get("HOOK_PHASE", "post") + event = "tool_start" if hook_phase == "pre" else "tool_complete" + # Extract fields - Claude Code hook format - hook_type = data.get("hook_type", "unknown") # PreToolUse or PostToolUse tool_name = data.get("tool_name", data.get("tool", "unknown")) tool_input = data.get("tool_input", data.get("input", {})) tool_output = data.get("tool_output", data.get("output", "")) @@ -82,9 +92,6 @@ try: else: tool_output_str = str(tool_output)[:5000] - # Determine event type - event = "tool_start" if "Pre" in hook_type else "tool_complete" - print(json.dumps({ "parsed": True, "event": event, @@ -103,7 +110,8 @@ PARSED_OK=$(echo "$PARSED" | python3 -c "import json,sys; print(json.load(sys.st if [ "$PARSED_OK" != "True" ]; then # Fallback: log raw input for debugging timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - TIMESTAMP="$timestamp" echo "$INPUT_JSON" | python3 -c " + export TIMESTAMP="$timestamp" + echo "$INPUT_JSON" | python3 -c " import json, sys, os raw = sys.stdin.read()[:2000] print(json.dumps({'timestamp': os.environ['TIMESTAMP'], 'event': 'parse_error', 'raw': raw})) @@ -124,7 +132,8 @@ fi # Build and write observation timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") -TIMESTAMP="$timestamp" echo "$PARSED" | python3 -c " +export TIMESTAMP="$timestamp" +echo "$PARSED" | python3 -c " import json, sys, os parsed = json.load(sys.stdin) diff --git a/skills/continuous-learning-v2/hooks/observe.sh b/skills/continuous-learning-v2/hooks/observe.sh index 3169aab2..05b04c14 100755 --- a/skills/continuous-learning-v2/hooks/observe.sh +++ b/skills/continuous-learning-v2/hooks/observe.sh @@ -36,6 +36,9 @@ set -e +# Hook phase from CLI argument: "pre" (PreToolUse) or "post" (PostToolUse) +HOOK_PHASE="${1:-post}" + CONFIG_DIR="${HOME}/.claude/homunculus" OBSERVATIONS_FILE="${CONFIG_DIR}/observations.jsonl" MAX_FILE_SIZE_MB=10 @@ -57,15 +60,22 @@ if [ -z "$INPUT_JSON" ]; then fi # Parse using python via stdin pipe (safe for all JSON payloads) -PARSED=$(echo "$INPUT_JSON" | python3 -c ' +# Pass HOOK_PHASE via env var since Claude Code does not include hook type in stdin JSON +PARSED=$(echo "$INPUT_JSON" | HOOK_PHASE="$HOOK_PHASE" python3 -c ' import json import sys +import os try: data = json.load(sys.stdin) + # Determine event type from CLI argument passed via env var. + # Claude Code does NOT include a "hook_type" field in the stdin JSON, + # so we rely on the shell argument ("pre" or "post") instead. + hook_phase = os.environ.get("HOOK_PHASE", "post") + event = "tool_start" if hook_phase == "pre" else "tool_complete" + # Extract fields - Claude Code hook format - hook_type = data.get("hook_type", "unknown") # PreToolUse or PostToolUse tool_name = data.get("tool_name", data.get("tool", "unknown")) tool_input = data.get("tool_input", data.get("input", {})) tool_output = data.get("tool_output", data.get("output", "")) @@ -82,9 +92,6 @@ try: else: tool_output_str = str(tool_output)[:5000] - # Determine event type - event = "tool_start" if "Pre" in hook_type else "tool_complete" - print(json.dumps({ "parsed": True, "event": event,