-
Notifications
You must be signed in to change notification settings - Fork 0
fix(hooks): Claude never saw hook messages — systemMessage is user-only #135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -203,8 +203,11 @@ elif [[ "$VIOLATION_COUNT" -eq 0 ]]; then | |
| echo -e "${YELLOW}WARNINGS|$WARNING_COUNT warnings (review recommended)${NC}" | ||
| exit 0 | ||
| else | ||
| echo -e "${RED}BLOCKED|$VIOLATION_COUNT violations, $WARNING_COUNT warnings${NC}" | ||
| echo "" | ||
| echo "Fix violations before committing. These patterns indicate shortcuts that will cause problems later." | ||
| exit 1 | ||
| # Exit code 2 = blocking error, stderr is fed to Claude | ||
| echo "COMMIT INTEGRITY: $VIOLATION_COUNT violations found. Fix these before committing:" >&2 | ||
| for v in "${VIOLATIONS[@]}"; do | ||
| echo " - $v" >&2 | ||
| done | ||
| echo "These patterns indicate shortcuts that will cause problems later." >&2 | ||
|
Comment on lines
+206
to
+211
|
||
| exit 2 | ||
| fi | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -140,6 +140,10 @@ fi | |
| if [[ "$score" -gt 10 ]]; then | ||
| PREV_COUNT=$(cat "$STRUGGLE_COUNT_FILE" 2>/dev/null || echo "0") | ||
| NEW_COUNT=$((PREV_COUNT + 1)) | ||
| # High severity (score > 25) triggers inject on next prompt immediately | ||
| if [[ "$score" -gt 25 && "$NEW_COUNT" -lt 2 ]]; then | ||
| NEW_COUNT=2 | ||
| fi | ||
| echo "$NEW_COUNT" > "$STRUGGLE_COUNT_FILE" | ||
| else | ||
| echo "0" > "$STRUGGLE_COUNT_FILE" | ||
|
|
@@ -164,19 +168,8 @@ CONSECUTIVE=$(cat "$STRUGGLE_COUNT_FILE" 2>/dev/null || echo "0") | |
| done | ||
| } >> "$STRUGGLE_FILE" | ||
|
|
||
| # Trigger after 2+ consecutive struggling responses OR high single score | ||
| if [[ "$CONSECUTIVE" -ge 2 ]] || [[ "$score" -gt 25 ]]; then | ||
| # Build signals list with proper escaping | ||
| SIGNALS_LIST="" | ||
| for sig in "${signals[@]}"; do | ||
| SIGNALS_LIST="${SIGNALS_LIST}- ${sig}\\n" | ||
| done | ||
|
|
||
| cat <<EOF | ||
| { | ||
| "systemMessage": "<struggle-detected score=\"$score\" consecutive=\"$CONSECUTIVE\">\\n\\nClaude appears to be struggling with this problem.\\n\\nSignals detected:\\n${SIGNALS_LIST}\\nSuggestion: Use the Task tool with subagent_type='deep-think-partner' for thorough analysis.\\n\\nExample: \\\"I'm finding this complex. Want me to spawn a deep-thinker for a more thorough analysis?\\\"\\n</struggle-detected>" | ||
| } | ||
| EOF | ||
| fi | ||
| # Note: No JSON output needed here. The struggle-inject.sh (UserPromptSubmit) | ||
| # reads .blackboard/.struggle-count and injects additionalContext to Claude | ||
| # on the next prompt. This async Stop hook only does analysis + blackboard writes. | ||
|
Comment on lines
+171
to
+173
|
||
|
|
||
| exit 0 | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,43 @@ | ||||||||||||||||||||||||||
| #!/usr/bin/env bash | ||||||||||||||||||||||||||
| # ============================================================================= | ||||||||||||||||||||||||||
| # STRUGGLE INJECT - Delivers struggle detection to Claude via UserPromptSubmit | ||||||||||||||||||||||||||
| # ============================================================================= | ||||||||||||||||||||||||||
| # Trigger: UserPromptSubmit (before Claude processes the next prompt) | ||||||||||||||||||||||||||
| # Purpose: Read blackboard state from async struggle-detector and inject | ||||||||||||||||||||||||||
| # suggestion as additionalContext so Claude actually sees it. | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # The struggle-detector.sh (Stop, async) writes scoring data to: | ||||||||||||||||||||||||||
| # .blackboard/.struggle-count — consecutive struggling responses | ||||||||||||||||||||||||||
| # .blackboard/.struggle-signals — detailed signal log | ||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||
| # This hook reads those files and injects context when threshold is met. | ||||||||||||||||||||||||||
| # After injection, resets the counter to prevent repeated suggestions. | ||||||||||||||||||||||||||
| # ============================================================================= | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-}" | ||||||||||||||||||||||||||
| [[ -z "$PLUGIN_ROOT" ]] && exit 0 | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| BLACKBOARD="$PLUGIN_ROOT/.blackboard" | ||||||||||||||||||||||||||
| STRUGGLE_COUNT_FILE="$BLACKBOARD/.struggle-count" | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| # Check consecutive struggle count | ||||||||||||||||||||||||||
| CONSECUTIVE=$(cat "$STRUGGLE_COUNT_FILE" 2>/dev/null || echo "0") | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
| CONSECUTIVE=$(cat "$STRUGGLE_COUNT_FILE" 2>/dev/null || echo "0") | |
| CONSECUTIVE=$(cat "$STRUGGLE_COUNT_FILE" 2>/dev/null || echo "0") | |
| # Normalize to integer; default to 0 if empty or non-numeric | |
| if ! [[ "$CONSECUTIVE" =~ ^[0-9]+$ ]]; then | |
| CONSECUTIVE=0 | |
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve single-response struggle escalation
struggle-inject.sh now injects guidance only when .struggle-count is >= 2, but before this change the detector escalated on either two consecutive struggles or one very high score (score > 25). With the new split, a severe one-off struggle increments the count to 1 and no suggestion is injected on the next prompt unless a second bad response occurs, so the high-severity path was unintentionally removed.
Useful? React with 👍 / 👎.
Copilot
AI
Feb 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
struggle-inject.sh assumes jq is available. If jq is missing, the script will exit non-zero under set -e and the hook won’t inject any additionalContext. Other hooks in this plugin (e.g., truth-beacon.sh) include a jq/no-jq fallback—consider adding the same here so the hook degrades gracefully.
| # Inject as additionalContext — use jq for safe JSON construction | |
| MSG="STRUGGLE DETECTOR ($CONSECUTIVE consecutive uncertain responses): Signals — $RECENT_SIGNALS. Consider spawning a deep-think-partner agent (Task tool, subagent_type='deep-think-partner') for thorough analysis instead of continuing to iterate." | |
| jq -n --arg ctx "$MSG" '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":$ctx}}' | |
| # Inject as additionalContext — prefer jq for safe JSON construction, but degrade gracefully if missing | |
| MSG="STRUGGLE DETECTOR ($CONSECUTIVE consecutive uncertain responses): Signals — $RECENT_SIGNALS. Consider spawning a deep-think-partner agent (Task tool, subagent_type='deep-think-partner') for thorough analysis instead of continuing to iterate." | |
| if command -v jq >/dev/null 2>&1; then | |
| jq -n --arg ctx "$MSG" '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":$ctx}}' | |
| else | |
| # Fallback: emit JSON using basic shell escaping when jq is not available | |
| ESCAPED_MSG=$(printf '%s' "$MSG" | sed 's/\\/\\\\/g; s/"/\\"/g') | |
| printf '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"%s"}}\n' "$ESCAPED_MSG" | |
| fi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
action: warnrules for PreToolUse are currently dropped (function returns{}for warnings whenhook_eventisPreToolUse). This makes warnings effectively no-op for the most common hook path, and contradicts the documented "warn but allow" behavior. Consider emitting a user-visible warning (e.g., viasystemMessage) or, if supported by the Claude Code hooks spec, returning a PreToolUsehookSpecificOutputwithpermissionDecision: "allow"plus a reason field so Claude can see the warning without blocking.