Skip to content

Commit aa667dc

Browse files
committed
chore(plugin): align claude-code hooks with upstream
user-prompt-submit.sh: add Windows Git Bash/MSYS2 safe mode (builtin-only fast path avoids EDR-related fork hangs), extract parse_epoch helper with full RFC3339 timezone normalization, defer project detection to after first- message path. Keep mcp__plugin_engram_engram__* tool prefix for this fork. SKILL.md: update fallback instructions to reference engram setup claude-code instead of manual ToolSearch snippet; update line 124 to match upstream.
1 parent 3196b39 commit aa667dc

2 files changed

Lines changed: 130 additions & 19 deletions

File tree

plugin/claude-code/scripts/user-prompt-submit.sh

Lines changed: 125 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,120 @@
1313
ENGRAM_PORT="${ENGRAM_PORT:-7437}"
1414
ENGRAM_URL="http://127.0.0.1:${ENGRAM_PORT}"
1515

16-
# Load shared helpers
16+
# Windows Git Bash/MSYS2 can fail while forking helper processes under
17+
# enterprise Defender/EDR, which makes Claude Code wait on prompt submission.
18+
# Keep the Windows path bash-builtin-only: no jq, git, curl, date, dirname, cat,
19+
# touch, or command substitutions. It preserves first-message tool loading and
20+
# degrades subsequent save nudges to a fast no-op instead of risking a hang.
21+
is_windows_bash() {
22+
case "${OSTYPE:-}" in
23+
msys*|cygwin*|win32*) return 0 ;;
24+
esac
25+
[ -n "${MSYSTEM:-}" ] || [ -n "${MINGW_PREFIX:-}" ]
26+
}
27+
28+
set_json_string_value() {
29+
local key="$1"
30+
local json="$2"
31+
local pattern='"'"$key"'"[[:space:]]*:[[:space:]]*"([^"]*)"'
32+
JSON_VALUE=""
33+
if [[ "$json" =~ $pattern ]]; then
34+
JSON_VALUE="${BASH_REMATCH[1]}"
35+
fi
36+
}
37+
38+
sanitize_session_key_part() {
39+
local raw="$1"
40+
local safe=""
41+
local i char
42+
for (( i=0; i<${#raw}; i++ )); do
43+
char="${raw:i:1}"
44+
if [[ "$char" =~ [a-zA-Z0-9_-] ]]; then
45+
safe+="$char"
46+
else
47+
safe+="_"
48+
fi
49+
done
50+
JSON_VALUE="$safe"
51+
}
52+
53+
print_toolsearch_message() {
54+
printf '%s\n' '{"systemMessage":"CRITICAL FIRST ACTION — Execute this ToolSearch NOW before responding to the user:\nselect:mcp__plugin_engram_engram__mem_save,mcp__plugin_engram_engram__mem_search,mcp__plugin_engram_engram__mem_context,mcp__plugin_engram_engram__mem_session_summary,mcp__plugin_engram_engram__mem_get_observation,mcp__plugin_engram_engram__mem_save_prompt\n\nAfter loading tools, call mem_context to check for prior session history before responding."}'
55+
}
56+
57+
if is_windows_bash && [ "${ENGRAM_CLAUDE_WINDOWS_BASH_SAFE_MODE:-auto}" != "0" ]; then
58+
INPUT=""
59+
while IFS= read -r LINE || [ -n "$LINE" ]; do
60+
INPUT+="${LINE}"$'\n'
61+
done
62+
63+
set_json_string_value "session_id" "$INPUT"
64+
SESSION_ID="$JSON_VALUE"
65+
if [ -n "$SESSION_ID" ]; then
66+
sanitize_session_key_part "$SESSION_ID"
67+
SESSION_KEY="engram-claude-${JSON_VALUE}-tools-loaded"
68+
else
69+
SESSION_KEY="engram-claude-windows-$$-tools-loaded"
70+
fi
71+
STATE_DIR="${TMPDIR:-/tmp}"
72+
STATE_FILE="${STATE_DIR}/${SESSION_KEY}"
73+
74+
if [ ! -f "$STATE_FILE" ]; then
75+
: > "$STATE_FILE" 2>/dev/null || true
76+
print_toolsearch_message
77+
exit 0
78+
fi
79+
80+
printf '%s\n' '{}'
81+
exit 0
82+
fi
83+
84+
# Load shared helpers after the Windows-safe fast path so Git Bash does not fork
85+
# for dirname/pwd before deciding whether the safe path applies.
1786
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
1887
source "${SCRIPT_DIR}/_helpers.sh"
1988

2089
# Read hook input from stdin
2190
INPUT=$(cat)
2291
CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
2392
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
24-
PROJECT=$(detect_project "$CWD")
93+
94+
parse_epoch() {
95+
TS="$1"
96+
if [ -z "$TS" ]; then
97+
return 1
98+
fi
99+
100+
# Drop fractional seconds without dropping timezone information.
101+
if [[ "$TS" == *.* ]]; then
102+
TS_PREFIX="${TS%%.*}"
103+
TS_SUFFIX="${TS#*.}"
104+
case "$TS_SUFFIX" in
105+
*Z) TS="${TS_PREFIX}Z" ;;
106+
*+*) TS="${TS_PREFIX}+${TS_SUFFIX#*+}" ;;
107+
*-*) TS="${TS_PREFIX}-${TS_SUFFIX#*-}" ;;
108+
*) TS="$TS_PREFIX" ;;
109+
esac
110+
fi
111+
112+
# BSD date accepts numeric RFC3339 offsets with %z, but requires +HHMM.
113+
if [[ "$TS" =~ ^([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2})([+-][0-9]{2}):([0-9]{2})$ ]]; then
114+
TZ_TS="${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[3]}"
115+
date -j -f "%Y-%m-%dT%H:%M:%S%z" "$TZ_TS" "+%s" 2>/dev/null && return 0
116+
fi
117+
if [[ "$TS" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}[+-][0-9]{4}$ ]]; then
118+
date -j -f "%Y-%m-%dT%H:%M:%S%z" "$TS" "+%s" 2>/dev/null && return 0
119+
fi
120+
121+
if [[ "$TS" == *Z ]]; then
122+
Z_TS="${TS%Z}"
123+
date -j -u -f "%Y-%m-%dT%H:%M:%S" "$Z_TS" "+%s" 2>/dev/null && return 0
124+
fi
125+
126+
date -j -f "%Y-%m-%dT%H:%M:%S" "$TS" "+%s" 2>/dev/null \
127+
|| date -j -f "%Y-%m-%d %H:%M:%S" "$TS" "+%s" 2>/dev/null \
128+
|| date -d "$TS" "+%s" 2>/dev/null
129+
}
25130

26131
# Default: no injection
27132
OUTPUT="{}"
@@ -37,7 +142,8 @@ OUTPUT="{}"
37142
if [ -n "$SESSION_ID" ]; then
38143
SESSION_KEY="engram-claude-${SESSION_ID}-tools-loaded"
39144
else
40-
# No session ID available — key on project to avoid repeated injections
145+
# No session ID available — only then detect project for the fallback state key.
146+
PROJECT=$(detect_project "$CWD")
41147
SAFE_PROJECT=$(printf '%s' "${PROJECT:-unknown}" | tr -cs 'a-zA-Z0-9_-' '_')
42148
SESSION_KEY="engram-claude-${SAFE_PROJECT}-$$-tools-loaded"
43149
fi
@@ -50,18 +156,19 @@ if [ ! -f "$STATE_FILE" ]; then
50156
touch "$STATE_FILE" 2>/dev/null || true
51157

52158
# Inject ToolSearch + mem_context instruction.
53-
# Use --arg so jq handles all escaping; use printf to avoid echo interpreting \n.
54-
TOOL_MSG="CRITICAL FIRST ACTION — Execute this ToolSearch NOW before responding to the user:"$'\n'"select:mcp__plugin_engram_engram__mem_save,mcp__plugin_engram_engram__mem_search,mcp__plugin_engram_engram__mem_context,mcp__plugin_engram_engram__mem_session_summary,mcp__plugin_engram_engram__mem_get_observation,mcp__plugin_engram_engram__mem_save_prompt"$'\n\n'"After loading tools, call mem_context to check for prior session history before responding."
55-
OUTPUT=$(jq -n --arg msg "$TOOL_MSG" '{"systemMessage": $msg}')
56-
57-
printf '%s\n' "$OUTPUT"
159+
print_toolsearch_message
58160
exit 0
59161
fi
60162

61163
# ──────────────────────────────────────────────────────────────────────────────
62164
# SUBSEQUENT MESSAGES — existing save-nudge logic
63165
# ──────────────────────────────────────────────────────────────────────────────
64166

167+
# Detect project only after the first-message path has had a chance to return.
168+
if [ -z "${PROJECT:-}" ]; then
169+
PROJECT=$(detect_project "$CWD")
170+
fi
171+
65172
# Bail early if we can't determine the project
66173
if [ -z "$PROJECT" ]; then
67174
echo "$OUTPUT"
@@ -77,9 +184,11 @@ fi
77184

78185
# Check session age — skip nudge if session is new (< 5 minutes)
79186
if [ -n "$SESSION_START" ]; then
80-
SESSION_START_EPOCH=$(date -j -f "%Y-%m-%dT%H:%M:%S" "${SESSION_START%%.*}" "+%s" 2>/dev/null \
81-
|| date -d "${SESSION_START%%.*}" "+%s" 2>/dev/null \
82-
|| echo "0")
187+
SESSION_START_EPOCH=$(parse_epoch "$SESSION_START")
188+
if [ -z "$SESSION_START_EPOCH" ]; then
189+
echo "$OUTPUT"
190+
exit 0
191+
fi
83192
NOW_EPOCH=$(date "+%s")
84193
SESSION_AGE_SECS=$(( NOW_EPOCH - SESSION_START_EPOCH ))
85194

@@ -111,9 +220,11 @@ if [ -z "$LAST_SAVE_AT" ]; then
111220
fi
112221

113222
# Parse last save timestamp and compare to now
114-
LAST_EPOCH=$(date -j -f "%Y-%m-%dT%H:%M:%S" "${LAST_SAVE_AT%%.*}" "+%s" 2>/dev/null \
115-
|| date -d "${LAST_SAVE_AT%%.*}" "+%s" 2>/dev/null \
116-
|| echo "0")
223+
LAST_EPOCH=$(parse_epoch "$LAST_SAVE_AT")
224+
if [ -z "$LAST_EPOCH" ]; then
225+
echo "$OUTPUT"
226+
exit 0
227+
fi
117228
NOW_EPOCH=$(date "+%s")
118229
ELAPSED=$(( NOW_EPOCH - LAST_EPOCH ))
119230

plugin/claude-code/skills/memory/SKILL.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ They are available immediately — no manual ToolSearch needed.
1717
- `mem_get_observation`, `mem_suggest_topic_key`, `mem_update`
1818
- `mem_session_start`, `mem_session_end`, `mem_save_prompt`
1919

20-
**Fallback**: If tools are unexpectedly unavailable, trigger ToolSearch manually:
21-
```
22-
select:mcp__plugin_engram_engram__mem_save,mcp__plugin_engram_engram__mem_search,mcp__plugin_engram_engram__mem_context,mcp__plugin_engram_engram__mem_session_summary,mcp__plugin_engram_engram__mem_get_observation,mcp__plugin_engram_engram__mem_suggest_topic_key,mcp__plugin_engram_engram__mem_update,mcp__plugin_engram_engram__mem_session_start,mcp__plugin_engram_engram__mem_session_end,mcp__plugin_engram_engram__mem_save_prompt
23-
```
20+
**Fallback**: If tools are unexpectedly unavailable, run `engram setup claude-code`
21+
again and restart Claude Code. Setup repairs the durable MCP config and
22+
permissions allowlist for both current (`mcp__engram__...`) and older
23+
plugin-scoped (`mcp__plugin_engram_engram__...`) server ids.
2424

2525
Admin tools (deferred — use ToolSearch only if needed):
2626
- `mem_stats`, `mem_delete`, `mem_timeline`, `mem_capture_passive`
@@ -121,4 +121,4 @@ If you see a message about compaction or context reset:
121121
3. Only THEN continue working
122122

123123
Do not skip step 1. Without it, everything done before compaction is lost from memory.
124-
All core tools are loaded automatically by the hook at session start — use the fallback ToolSearch above if they are unexpectedly missing.
124+
All core tools are loaded automatically by the hook at session start. If they are unexpectedly missing, rerun `engram setup claude-code` and restart Claude Code.

0 commit comments

Comments
 (0)