fix(plugin): mem0-plugin hook cleanup

Six independent fixes that make hook behaviour deterministic, debuggable,
and free of silent footguns.

Persist session_id in stored memories
  on_pre_compact.py now extracts session_id from the hook input and writes
  it under metadata.session_id, enabling search-by-session.

Skip SessionStart bootstrap when MEM0_API_KEY is missing
  on_session_start.sh exits 0 early instead of emitting bootstrap text that
  instructs the agent to call mem0 MCP tools that would all fail without
  a key.

Tighten block_memory_write.sh regex
  The old `*/memory/*.md` pattern blocked legitimate paths like
  docs/memory/architecture.md. New pattern blocks `*/MEMORY.md` and
  `*/.claude/memory/*` only -- the actual surfaces we want to protect.

Opt-in debug logging via MEM0_DEBUG=1
  Each hook (bash + python) redirects stderr / adds a FileHandler to
  ~/.mem0/hooks.log when MEM0_DEBUG is set. No helper module, no
  rotation -- KISS. Default behaviour is unchanged.

Drop the duplicate PreCompact registration
  hooks.json and cursor-hooks.json had on_pre_compact.sh AND
  on_pre_compact.py registered on PreCompact, producing duplicate
  session-state writes. Drop the python entry. The python script stays
  alive -- on_stop.sh still spawns it as a session-end safety net.

CI nudge
  Adds a single comment line to pyproject.toml because the repo's
  ci.yml path filters exclude mem0-plugin/-only PRs but the
  build_mem0/build_embedchain status checks are required by branch
  protection. Track the proper fix (path-conditional CI) as a
  follow-up.
This commit is contained in:
Mgeeeek
2026-05-07 21:53:49 +05:30
parent 58304fc939
commit 8cb4f09ded
11 changed files with 50 additions and 13 deletions
+5 -1
View File
@@ -13,6 +13,10 @@
set -euo pipefail
if [ -n "${MEM0_DEBUG:-}" ]; then
mkdir -p "$HOME/.mem0" && exec 2>>"$HOME/.mem0/hooks.log"
fi
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""' 2>/dev/null || echo "")
@@ -22,7 +26,7 @@ if [ -z "$FILE_PATH" ]; then
fi
case "$FILE_PATH" in
*/MEMORY.md|*/memory/*.md|*/.claude/*/memory/*)
*/MEMORY.md|*/.claude/memory/*)
echo "BLOCKED: Do not write to $FILE_PATH. Use the mem0 MCP \`add_memory\` tool instead to persist memories. This project uses mem0 for all memory storage." >&2
exit 2
;;
+14 -2
View File
@@ -27,6 +27,16 @@ _handler = logging.StreamHandler(sys.stderr)
_handler.setFormatter(logging.Formatter("[mem0-capture] %(message)s"))
log.addHandler(_handler)
if os.environ.get("MEM0_DEBUG"):
_log_dir = os.path.expanduser("~/.mem0")
try:
os.makedirs(_log_dir, exist_ok=True)
_file_handler = logging.FileHandler(os.path.join(_log_dir, "hooks.log"))
_file_handler.setFormatter(logging.Formatter("[mem0-capture] %(asctime)s %(message)s"))
log.addHandler(_file_handler)
except OSError:
pass
API_URL = "https://api.mem0.ai"
MAX_TAIL_LINES = 500
MAX_USER_MESSAGES = 30
@@ -149,7 +159,7 @@ def build_content(state: dict, source: str) -> str:
return "\n".join(parts)
def store_memory(api_key: str, content: str, user_id: str, source: str) -> bool:
def store_memory(api_key: str, content: str, user_id: str, source: str, session_id: str = "") -> bool:
"""Store session state as a memory via the Mem0 REST API."""
body = {
"messages": [
@@ -159,6 +169,7 @@ def store_memory(api_key: str, content: str, user_id: str, source: str) -> bool:
"metadata": {
"type": "session_state",
"source": source,
"session_id": session_id,
},
}
@@ -207,6 +218,7 @@ def main():
log.debug("No transcript_path provided")
return
session_id = hook_input.get("session_id", "")
user_id = os.environ.get("MEM0_USER_ID", os.environ.get("USER", "default"))
lines = tail_lines(transcript_path, MAX_TAIL_LINES)
@@ -228,7 +240,7 @@ def main():
len(state["bash_commands"]),
)
store_memory(api_key, content, user_id, source)
store_memory(api_key, content, user_id, source, session_id)
if __name__ == "__main__":
+4
View File
@@ -11,6 +11,10 @@
set -euo pipefail
if [ -n "${MEM0_DEBUG:-}" ]; then
mkdir -p "$HOME/.mem0" && exec 2>>"$HOME/.mem0/hooks.log"
fi
cat <<'EOF'
## CRITICAL: Pre-Compaction Session Summary
+10
View File
@@ -11,6 +11,16 @@
# even if jq is missing or stdin is malformed.
set -uo pipefail
if [ -n "${MEM0_DEBUG:-}" ]; then
mkdir -p "$HOME/.mem0" && exec 2>>"$HOME/.mem0/hooks.log"
fi
# Skip the bootstrap entirely if no API key is configured -- the agent
# would otherwise be told to call mem0 MCP tools that will all fail.
if [ -z "${MEM0_API_KEY:-}" ]; then
exit 0
fi
INPUT=$(cat)
SOURCE=$(echo "$INPUT" | jq -r '.source // "startup"' 2>/dev/null || echo "startup")
+4
View File
@@ -12,6 +12,10 @@
set -euo pipefail
if [ -n "${MEM0_DEBUG:-}" ]; then
mkdir -p "$HOME/.mem0" && exec 2>>"$HOME/.mem0/hooks.log"
fi
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
INPUT=$(cat)
+4
View File
@@ -17,6 +17,10 @@
set -uo pipefail
if [ -n "${MEM0_DEBUG:-}" ]; then
mkdir -p "$HOME/.mem0" && exec 2>>"$HOME/.mem0/hooks.log"
fi
INPUT=$(cat)
STOP_HOOK_ACTIVE=$(echo "$INPUT" | jq -r '.stop_hook_active // false' 2>/dev/null || echo "false")
+4
View File
@@ -9,6 +9,10 @@
set -euo pipefail
if [ -n "${MEM0_DEBUG:-}" ]; then
mkdir -p "$HOME/.mem0" && exec 2>>"$HOME/.mem0/hooks.log"
fi
INPUT=$(cat)
TASK_SUBJECT=$(echo "$INPUT" | jq -r '.task_subject // "unknown task"' 2>/dev/null || echo "unknown task")
+4
View File
@@ -13,6 +13,10 @@
# must never block the user's prompt.
set -uo pipefail
if [ -n "${MEM0_DEBUG:-}" ]; then
mkdir -p "$HOME/.mem0" && exec 2>>"$HOME/.mem0/hooks.log"
fi
INPUT=$(cat)
PROMPT=$(echo "$INPUT" | jq -r '.prompt // ""' 2>/dev/null || echo "")