fix(mem0-agent): bundle a launcher so hooks never depend on a global CLI

SessionStart failed with 'pyenv: mem0-agent: command not found' in any repo whose
.python-version selects a Python the console script was not installed into. A
pip entry point is the wrong dependency for a plugin.

- bin/mem0-agent: POSIX launcher that picks the first CPython >= 3.10 it can find
  and runs the package from source (there are no third-party dependencies)
- hooks now invoke ${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent, matching how the v1
  plugin calls its own scripts; the generator enforces this
- app_id override renamed to MEM0_AGENT_APP_ID: v1 exports MEM0_PROJECT_ID, which
  was silently overriding this plugin's project scope while both are installed

Verified: all six hooks exit 0 from a pyenv-3.11 repo, and app_id resolves per
repository again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Deshraj Yadav
2026-07-29 16:20:28 -07:00
co-authored by Claude Fable 5
parent a07756f6d7
commit dda5f21332
7 changed files with 93 additions and 20 deletions
+10 -2
View File
@@ -215,8 +215,16 @@ def validate(spec: dict) -> dict:
raise SpecError(
f"hook {entry['id']!r} runs on {entry['event']} and must declare local_only: true"
)
if not str(entry["command"]).startswith("mem0-agent "):
raise SpecError(f"hook {entry['id']!r}: command must invoke the mem0-agent CLI")
# Commands must invoke the plugin's own bundled launcher. A bare console script is
# not dependable: under pyenv the shim resolves against whichever Python version the
# current directory selects, so a repo pinning a different version fails with
# "pyenv: mem0-agent: command not found".
cmd = str(entry["command"])
if not cmd.startswith("${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent "):
raise SpecError(
f"hook {entry['id']!r}: command must invoke "
"${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent"
)
return spec
+6 -6
View File
@@ -6,7 +6,7 @@
"hooks": [
{
"type": "command",
"command": "MEM0_EDITOR=claude-code mem0-agent context --session-id \"$CLAUDE_SESSION_ID\"",
"command": "MEM0_EDITOR=claude-code ${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent context --session-id \"$CLAUDE_SESSION_ID\"",
"timeout": 10,
"statusMessage": "Loading mem0 context..."
}
@@ -18,7 +18,7 @@
"hooks": [
{
"type": "command",
"command": "MEM0_EDITOR=claude-code MEM0_LOCAL_ONLY=1 mem0-agent observe --source prompt",
"command": "MEM0_EDITOR=claude-code MEM0_LOCAL_ONLY=1 ${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent observe --source prompt",
"timeout": 3
}
]
@@ -30,7 +30,7 @@
"hooks": [
{
"type": "command",
"command": "(MEM0_EDITOR=claude-code mem0-agent assist-error >/dev/null 2>&1 &)",
"command": "(MEM0_EDITOR=claude-code ${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent assist-error >/dev/null 2>&1 &)",
"timeout": 5
}
]
@@ -41,7 +41,7 @@
"hooks": [
{
"type": "command",
"command": "(MEM0_EDITOR=claude-code mem0-agent flush >/dev/null 2>&1 &)",
"command": "(MEM0_EDITOR=claude-code ${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent flush >/dev/null 2>&1 &)",
"timeout": 5
}
]
@@ -52,7 +52,7 @@
"hooks": [
{
"type": "command",
"command": "(MEM0_EDITOR=claude-code mem0-agent flush --reason precompact >/dev/null 2>&1 &)",
"command": "(MEM0_EDITOR=claude-code ${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent flush --reason precompact >/dev/null 2>&1 &)",
"timeout": 5
}
]
@@ -63,7 +63,7 @@
"hooks": [
{
"type": "command",
"command": "(MEM0_EDITOR=claude-code mem0-agent flush --reason end >/dev/null 2>&1 &)",
"command": "(MEM0_EDITOR=claude-code ${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent flush --reason end >/dev/null 2>&1 &)",
"timeout": 5
}
]
@@ -1,4 +1,4 @@
# mem0-agent hook wiring -- the single source of truth.
# "${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent" hook wiring -- the single source of truth.
#
# v1 hand-maintained four editor dialects (Claude Code, Cursor, Codex, Antigravity).
# They drifted: an event existed in one file and not another, timeouts disagreed, and
@@ -71,7 +71,7 @@ hooks:
- id: session-context
event: "SessionStart"
matcher: "startup|resume|compact"
command: "mem0-agent context --session-id {session_id}"
command: "${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent context --session-id {session_id}"
timeout: 10
background: false
blocking: true
@@ -82,7 +82,7 @@ hooks:
- id: prompt-observe
event: "UserPromptSubmit"
matcher: null
command: "mem0-agent observe --source prompt"
command: "${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent observe --source prompt"
timeout: 3
background: false
blocking: true
@@ -93,7 +93,7 @@ hooks:
- id: bash-assist-error
event: "PostToolUse"
matcher: "Bash"
command: "mem0-agent assist-error"
command: "${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent assist-error"
timeout: 5
background: true
blocking: false
@@ -104,7 +104,7 @@ hooks:
- id: stop-flush
event: "Stop"
matcher: null
command: "mem0-agent flush"
command: "${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent flush"
timeout: 5
background: true
blocking: false
@@ -115,7 +115,7 @@ hooks:
- id: precompact-flush
event: "PreCompact"
matcher: null
command: "mem0-agent flush --reason precompact"
command: "${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent flush --reason precompact"
timeout: 5
background: true
blocking: false
@@ -126,7 +126,7 @@ hooks:
- id: sessionend-flush
event: "SessionEnd"
matcher: null
command: "mem0-agent flush --reason end"
command: "${CLAUDE_PLUGIN_ROOT}/bin/mem0-agent flush --reason end"
timeout: 5
background: true
blocking: false