555e282cc4
pi-agent-plugin checks / lint (push) Has been cancelled
pi-agent-plugin checks / test (20) (push) Has been cancelled
pi-agent-plugin checks / test (22) (push) Has been cancelled
pi-agent-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / check_changes (push) Has been cancelled
TypeScript SDK CI / changelog_check (push) Has been cancelled
ci / changelog_check (push) Has been cancelled
ci / check_changes (push) Has been cancelled
ci / build_mem0 (3.10) (push) Has been cancelled
ci / build_mem0 (3.11) (push) Has been cancelled
ci / build_mem0 (3.12) (push) Has been cancelled
CLI Node CI / lint (push) Has been cancelled
CLI Node CI / test (20) (push) Has been cancelled
CLI Node CI / test (22) (push) Has been cancelled
CLI Node CI / build (push) Has been cancelled
CLI Python CI / lint (push) Has been cancelled
CLI Python CI / test (3.10) (push) Has been cancelled
CLI Python CI / test (3.11) (push) Has been cancelled
CLI Python CI / test (3.12) (push) Has been cancelled
CLI Python CI / build (push) Has been cancelled
openclaw checks / lint (push) Has been cancelled
openclaw checks / test (20) (push) Has been cancelled
openclaw checks / test (22) (push) Has been cancelled
openclaw checks / build (push) Has been cancelled
opencode-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (22) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (22) (push) Has been cancelled
124 lines
3.9 KiB
Bash
Executable File
124 lines
3.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Hook: PostToolUse (matcher: Bash)
|
|
#
|
|
# Scans bash command output for stack traces and error patterns.
|
|
# When found, injects a search rubric telling the agent to check mem0
|
|
# for prior occurrences of the same error.
|
|
#
|
|
# This complements on_user_prompt.sh (which catches errors in the user's
|
|
# typed message). This hook catches errors in COMMAND OUTPUT — e.g.,
|
|
# when `npm test` or `python script.py` fails with a traceback.
|
|
#
|
|
# Input: JSON on stdin with tool_name, tool_input, tool_response
|
|
# Output: Context injected into Claude's next response (exit 0)
|
|
|
|
set -uo pipefail
|
|
|
|
INPUT=$(cat)
|
|
|
|
TOOL_RESULT=$(echo "$INPUT" | jq -r '.tool_response // ""' 2>/dev/null || echo "")
|
|
|
|
# Skip short output (< 50 chars unlikely to contain a real stack trace)
|
|
if [ ${#TOOL_RESULT} -lt 50 ]; then
|
|
exit 0
|
|
fi
|
|
|
|
# Skip git operations — not useful for error detection
|
|
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
|
|
case "$COMMAND" in
|
|
*"git commit"*|*"git merge"*|*"git rebase"*)
|
|
exit 0
|
|
;;
|
|
esac
|
|
|
|
# Detect stack traces and error patterns in command output
|
|
HAS_ERROR=""
|
|
if echo "$TOOL_RESULT" | grep -qE '(Traceback \(most recent call last\)|panic: |FATAL:|error\[E[0-9]+\])'; then
|
|
HAS_ERROR="true"
|
|
elif [ "$(echo "$TOOL_RESULT" | grep -cE '(Error:|Exception:)')" -ge 2 ]; then
|
|
HAS_ERROR="true"
|
|
fi
|
|
|
|
if [ -z "$HAS_ERROR" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
. "$SCRIPT_DIR/_identity.sh" 2>/dev/null || true
|
|
|
|
# Extract error class/message (first matching line)
|
|
ERROR_LINE=$(echo "$TOOL_RESULT" | grep -iE '(Error:|Exception:|panic:|FAIL:|fatal:)' | head -1 | sed 's/^[[:space:]]*//' | cut -c1-120)
|
|
|
|
# Extract file paths from stack trace frames
|
|
TRACE_FILES=$(echo "$TOOL_RESULT" | grep -oE '([a-zA-Z0-9_./-]+\.(py|ts|tsx|js|jsx|rs|go|rb|java|sh))(:[0-9]+)?' | head -5 | sort -u)
|
|
|
|
# Build file list for display
|
|
FILE_DISPLAY=""
|
|
if [ -n "$TRACE_FILES" ]; then
|
|
FILE_DISPLAY=$(echo "$TRACE_FILES" | sed 's/^/ - /')
|
|
fi
|
|
|
|
USER_ID="${MEM0_RESOLVED_USER_ID:-${USER:-default}}"
|
|
|
|
# Extract query (first 80 chars of error line)
|
|
ERROR_QUERY=$(echo "$ERROR_LINE" | cut -c1-80)
|
|
|
|
# Telemetry (fire regardless of API key)
|
|
python3 "$SCRIPT_DIR/telemetry.py" bash_error --error_detected 2>/dev/null &
|
|
|
|
# No API key — skip output entirely
|
|
if [ -z "${MEM0_API_KEY:-}" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
# Pre-fetch memories: anti_pattern and bug_fix searches
|
|
RESULTS=$(PYTHONPATH="$SCRIPT_DIR" MEM0_SEARCH_QUERY="$ERROR_QUERY" MEM0_SEARCH_USER="$USER_ID" \
|
|
MEM0_API_KEY="${MEM0_API_KEY}" MEM0_PROJECT_ID="${MEM0_PROJECT_ID:-unknown}" \
|
|
python3 -c "
|
|
import os, sys
|
|
sys.path.insert(0, os.environ.get('PYTHONPATH', '.'))
|
|
from _search import search_memories, format_results_for_context, should_rerank
|
|
|
|
api_key = os.environ.get('MEM0_API_KEY', '')
|
|
user_id = os.environ.get('MEM0_SEARCH_USER', 'default')
|
|
project_id = os.environ.get('MEM0_PROJECT_ID', 'unknown')
|
|
query = os.environ.get('MEM0_SEARCH_QUERY', '')
|
|
rerank = should_rerank()
|
|
|
|
r1 = search_memories(api_key, user_id, project_id, query, metadata_type='anti_pattern', top_k=3, rerank=rerank)
|
|
r2 = search_memories(api_key, user_id, project_id, query, metadata_type='bug_fix', top_k=3, rerank=rerank)
|
|
|
|
seen = set()
|
|
combined = []
|
|
for m in r1 + r2:
|
|
mid = m.get('id', '')
|
|
if mid not in seen:
|
|
seen.add(mid)
|
|
combined.append(m)
|
|
|
|
print(format_results_for_context(combined, heading='Prior error memories'), end='')
|
|
" 2>/dev/null || echo "")
|
|
|
|
# Build context string for JSON output
|
|
CTX="Error detected in command output\n\n"
|
|
CTX="${CTX}\`${COMMAND}\` produced an error:\n> ${ERROR_LINE}\n"
|
|
|
|
if [ -n "$FILE_DISPLAY" ]; then
|
|
CTX="${CTX}\nFiles in stack trace:\n${FILE_DISPLAY}\n"
|
|
fi
|
|
|
|
if [ -n "$RESULTS" ]; then
|
|
CTX="${CTX}\n${RESULTS}\n"
|
|
fi
|
|
|
|
CTX="${CTX}\nResolved errors are stored as anti_pattern or bug_fix memories for future reference."
|
|
|
|
jq -cn --arg ctx "$CTX" '{
|
|
hookSpecificOutput: {
|
|
hookEventName: "PostToolUse",
|
|
additionalContext: $ctx
|
|
}
|
|
}' 2>/dev/null || true
|
|
|
|
exit 0
|