Add Claude Code skills Memanto bridge#549
Conversation
📝 WalkthroughWalkthroughThis PR adds a complete example demonstrating how to bridge Claude Code skills with shared engineering memory via Memanto. It implements memory extraction from skill transcripts, configurable storage backends (JSONL, Memanto CLI, Memanto SDK), context injection for subsequent skill runs, installation automation, and comprehensive validation tests. ChangesMemanto Skills Bridge Implementation
Sequence Diagram(s)sequenceDiagram
participant User
participant SkillRun as Skill Run (with hooks)
participant Bridge as skill_memory_bridge.py
participant Backend as Memory Backend
participant Claude as Claude Code
User->>SkillRun: Invokes skill via Claude
SkillRun->>Bridge: UserPromptSubmit hook: inject
Bridge->>Backend: recall(task_query, limit=5)
Backend-->>Bridge: list[EngineeringMemory]
Bridge->>Bridge: format_context()
Bridge-->>Claude: MEMANTO_SKILL_CONTEXT env var
Claude->>SkillRun: Skill executes with injected context
SkillRun->>SkillRun: Produces transcript output
SkillRun->>Bridge: Stop hook: run-skill
Bridge->>Bridge: extract_engineering_memories(transcript)
Bridge->>Bridge: should_skip_memory() filter
Bridge->>Backend: remember(typed_memory)
Backend-->>Bridge: ✓ stored
Bridge-->>SkillRun: stored_memories=N output
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
examples/claudecode-skills-memanto/skill_memory_bridge.py (1)
138-147: ⚡ Quick winBroad exception catch with string matching is fragile.
Catching all
Exceptiontypes and checking for"already exists"in the string representation could mask unrelated errors if they coincidentally contain that substring.♻️ Proposed improvement
def _ensure_agent(self) -> None: try: self.client.create_agent( self.agent_id, pattern="tool", description="Claude Code skills memory bridge", ) - except Exception as exc: - if "already exists" not in str(exc).lower(): + except Exception as exc: + exc_str = str(exc).lower() + if "already exists" not in exc_str and "conflict" not in exc_str: raiseAlternatively, check if the Memanto SDK exposes a specific exception type (e.g.,
AgentExistsErrororConflictError) that can be caught directly.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/claudecode-skills-memanto/skill_memory_bridge.py` around lines 138 - 147, The _ensure_agent method currently catches all Exception and filters by string matching; replace this with catching the SDK's specific "already exists" exception (e.g., AgentExistsError or ConflictError) thrown by client.create_agent by importing that exception from the Memanto SDK and using an explicit except AgentExistsError: block to silently ignore the agent-exists case, and keep a general except to re-raise unexpected errors (or remove the broad catch entirely) so unrelated errors are not masked; reference _ensure_agent and client.create_agent when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/claudecode-skills-memanto/claude-settings.snippet.json`:
- Around line 9-20: The command strings for invoking skill_memory_bridge.py (the
"command" values running "python3
.../examples/claudecode-skills-memanto/skill_memory_bridge.py inject --skill
claude-code ..." and the run-skill variant) rely on the current working
directory ($PWD) and are therefore not portable; update both command entries to
use an absolute or repo-root placeholder path for the bridge script (e.g.,
${REPO_ROOT}/examples/claudecode-skills-memanto/skill_memory_bridge.py) or
otherwise resolve the script path explicitly instead of depending on $PWD, and
keep the existing flags (inject/run-skill, --skill, --task, --cwd, --output)
unchanged.
In `@examples/claudecode-skills-memanto/install.sh`:
- Around line 30-60: The installer mutates settings["hooks"] and assumes each
event entry is a list of dicts with a "hooks" list containing dicts with
"command", which can crash for nonstandard shapes; before using hooks =
settings.setdefault("hooks", {}), validate/coerce settings["hooks"] to a dict,
and for each event ensure existing is a list (coerce/replace non-lists), ensure
each item in existing is a dict with a "hooks" list (coerce/replace), and ensure
each hook is a dict before reading hook.get("command"); then perform the
duplicate check using these normalized structures and append entry safely
(referencing variables hooks, snippet, existing, entry, command in the loop).
In `@examples/claudecode-skills-memanto/README.md`:
- Around line 16-20: The Quick Start in README.md runs validate.py and pytest
without installing dependencies; update the instructions to run pip install -r
requirements.txt before invoking validate.py and python -m pytest
test_skill_memory_bridge.py so first-time users have required packages available
(add the pip install step immediately prior to the validate.py and pytest
commands).
In `@examples/claudecode-skills-memanto/skill_memory_bridge.py`:
- Around line 89-114: The subprocess.run calls in remember() and recall() lack a
timeout which can cause the process to hang indefinitely; update both calls to
include a sensible timeout (e.g., a few seconds) and handle
subprocess.TimeoutExpired by catching it where remember() and recall() are
defined, logging or re-raising a controlled error; specifically modify the
subprocess.run invocation in the remember() method and the one in recall() to
pass timeout=<seconds> and add try/except around them to catch
subprocess.TimeoutExpired (and optionally subprocess.CalledProcessError) to
ensure the hook fails fast and cleans up gracefully.
- Around line 64-79: The recall loop currently calls json.loads(line) which will
raise JSONDecodeError on malformed JSON and abort the whole method; wrap the
load in a try/except (catch json.JSONDecodeError and optionally ValueError)
around the json.loads(line) call inside the for-loop in recall (the block that
constructs EngineeringMemory, tokenizes via tokenize, computes score from
query_terms & haystack, and appends to scored) and on exception log or warn
(e.g., mention the offending line or offset) and continue to the next line so a
single bad JSONL row does not break processing of valid memories.
In `@examples/claudecode-skills-memanto/uninstall.sh`:
- Around line 24-27: The uninstall filter currently rejects any entry whose
hooks contain a command substring "skill_memory_bridge.py", which is too broad;
update the logic in uninstall.sh where commands = [hook.get("command", "") for
hook in entry.get("hooks", [])] and the subsequent any(...) check to only skip
entries whose hook command exactly matches (or matches one of a small whitelist
of) the installer-owned commands (e.g., full command strings the installer
created) instead of using substring matching — compare command ==
"exact_owned_command" or command in {"owned_cmd1", "owned_cmd2"} (or
normalize/resolve paths before comparing) and only then continue; leave
kept.append(entry) unchanged.
- Line 18: Wrap the json.loads(settings_path.read_text(encoding="utf-8")) call
in a try/except that catches json.JSONDecodeError, print a clean, user-facing
error mentioning the malformed settings.json (using settings_path to show the
file) to stderr, and exit with a non-zero status (e.g., sys.exit(1)); update the
code that assigns settings so the exception is handled instead of letting a
traceback propagate.
---
Nitpick comments:
In `@examples/claudecode-skills-memanto/skill_memory_bridge.py`:
- Around line 138-147: The _ensure_agent method currently catches all Exception
and filters by string matching; replace this with catching the SDK's specific
"already exists" exception (e.g., AgentExistsError or ConflictError) thrown by
client.create_agent by importing that exception from the Memanto SDK and using
an explicit except AgentExistsError: block to silently ignore the agent-exists
case, and keep a general except to re-raise unexpected errors (or remove the
broad catch entirely) so unrelated errors are not masked; reference
_ensure_agent and client.create_agent when making the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 628b8115-e2e3-4b31-b229-af30e71d3d79
📒 Files selected for processing (10)
examples/claudecode-skills-memanto/.env.exampleexamples/claudecode-skills-memanto/README.mdexamples/claudecode-skills-memanto/claude-settings.snippet.jsonexamples/claudecode-skills-memanto/demo_transcript.mdexamples/claudecode-skills-memanto/install.shexamples/claudecode-skills-memanto/requirements.txtexamples/claudecode-skills-memanto/skill_memory_bridge.pyexamples/claudecode-skills-memanto/test_skill_memory_bridge.pyexamples/claudecode-skills-memanto/uninstall.shexamples/claudecode-skills-memanto/validate.py
| "command": "python3 examples/claudecode-skills-memanto/skill_memory_bridge.py inject --skill claude-code --task \"$CLAUDE_USER_PROMPT\" --cwd \"$PWD\"" | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "Stop": [ | ||
| { | ||
| "matcher": "", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "python3 examples/claudecode-skills-memanto/skill_memory_bridge.py run-skill --skill claude-code --task \"$CLAUDE_USER_PROMPT\" --cwd \"$PWD\" --output \"$(cat)\"" |
There was a problem hiding this comment.
Use an absolute (or placeholder) bridge path in the snippet.
These commands are not portable as-is because they depend on current working directory being the repo root.
Suggested fix
- "command": "python3 examples/claudecode-skills-memanto/skill_memory_bridge.py inject --skill claude-code --task \"$CLAUDE_USER_PROMPT\" --cwd \"$PWD\""
+ "command": "python3 \"__ABSOLUTE_PATH_TO_REPO__/examples/claudecode-skills-memanto/skill_memory_bridge.py\" inject --skill claude-code --task \"$CLAUDE_USER_PROMPT\" --cwd \"$PWD\""
@@
- "command": "python3 examples/claudecode-skills-memanto/skill_memory_bridge.py run-skill --skill claude-code --task \"$CLAUDE_USER_PROMPT\" --cwd \"$PWD\" --output \"$(cat)\""
+ "command": "python3 \"__ABSOLUTE_PATH_TO_REPO__/examples/claudecode-skills-memanto/skill_memory_bridge.py\" run-skill --skill claude-code --task \"$CLAUDE_USER_PROMPT\" --cwd \"$PWD\" --output \"$(cat)\""📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "command": "python3 examples/claudecode-skills-memanto/skill_memory_bridge.py inject --skill claude-code --task \"$CLAUDE_USER_PROMPT\" --cwd \"$PWD\"" | |
| } | |
| ] | |
| } | |
| ], | |
| "Stop": [ | |
| { | |
| "matcher": "", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "python3 examples/claudecode-skills-memanto/skill_memory_bridge.py run-skill --skill claude-code --task \"$CLAUDE_USER_PROMPT\" --cwd \"$PWD\" --output \"$(cat)\"" | |
| "command": "python3 \"__ABSOLUTE_PATH_TO_REPO__/examples/claudecode-skills-memanto/skill_memory_bridge.py\" inject --skill claude-code --task \"$CLAUDE_USER_PROMPT\" --cwd \"$PWD\"" | |
| } | |
| ] | |
| } | |
| ], | |
| "Stop": [ | |
| { | |
| "matcher": "", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "python3 \"__ABSOLUTE_PATH_TO_REPO__/examples/claudecode-skills-memanto/skill_memory_bridge.py\" run-skill --skill claude-code --task \"$CLAUDE_USER_PROMPT\" --cwd \"$PWD\" --output \"$(cat)\"" |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/claudecode-skills-memanto/claude-settings.snippet.json` around lines
9 - 20, The command strings for invoking skill_memory_bridge.py (the "command"
values running "python3
.../examples/claudecode-skills-memanto/skill_memory_bridge.py inject --skill
claude-code ..." and the run-skill variant) rely on the current working
directory ($PWD) and are therefore not portable; update both command entries to
use an absolute or repo-root placeholder path for the bridge script (e.g.,
${REPO_ROOT}/examples/claudecode-skills-memanto/skill_memory_bridge.py) or
otherwise resolve the script path explicitly instead of depending on $PWD, and
keep the existing flags (inject/run-skill, --skill, --task, --cwd, --output)
unchanged.
| hooks = settings.setdefault("hooks", {}) | ||
| snippet = { | ||
| "UserPromptSubmit": { | ||
| "matcher": "", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": f'python3 "{bridge}" inject --skill claude-code --task "$CLAUDE_USER_PROMPT" --cwd "$PWD"', | ||
| } | ||
| ], | ||
| }, | ||
| "Stop": { | ||
| "matcher": "", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": f'python3 "{bridge}" run-skill --skill claude-code --task "$CLAUDE_USER_PROMPT" --cwd "$PWD" --output "$(cat)"', | ||
| } | ||
| ], | ||
| }, | ||
| } | ||
|
|
||
| for event, entry in snippet.items(): | ||
| existing = hooks.setdefault(event, []) | ||
| command = entry["hooks"][0]["command"] | ||
| if not any( | ||
| hook.get("command") == command | ||
| for item in existing | ||
| for hook in item.get("hooks", []) | ||
| ): | ||
| existing.append(entry) |
There was a problem hiding this comment.
Defensively normalize existing hook structure before mutating it.
The installer assumes hooks and event entries are well-typed. A nonstandard settings.json shape can crash installation during mutation.
Suggested fix
-hooks = settings.setdefault("hooks", {})
+hooks = settings.get("hooks")
+if not isinstance(hooks, dict):
+ hooks = {}
+ settings["hooks"] = hooks
@@
for event, entry in snippet.items():
- existing = hooks.setdefault(event, [])
+ existing = hooks.get(event)
+ if not isinstance(existing, list):
+ existing = []
+ hooks[event] = existing
command = entry["hooks"][0]["command"]
if not any(
hook.get("command") == command
- for item in existing
- for hook in item.get("hooks", [])
+ for item in existing
+ if isinstance(item, dict)
+ for hook in item.get("hooks", [])
+ if isinstance(item.get("hooks", []), list)
):
existing.append(entry)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| hooks = settings.setdefault("hooks", {}) | |
| snippet = { | |
| "UserPromptSubmit": { | |
| "matcher": "", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": f'python3 "{bridge}" inject --skill claude-code --task "$CLAUDE_USER_PROMPT" --cwd "$PWD"', | |
| } | |
| ], | |
| }, | |
| "Stop": { | |
| "matcher": "", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": f'python3 "{bridge}" run-skill --skill claude-code --task "$CLAUDE_USER_PROMPT" --cwd "$PWD" --output "$(cat)"', | |
| } | |
| ], | |
| }, | |
| } | |
| for event, entry in snippet.items(): | |
| existing = hooks.setdefault(event, []) | |
| command = entry["hooks"][0]["command"] | |
| if not any( | |
| hook.get("command") == command | |
| for item in existing | |
| for hook in item.get("hooks", []) | |
| ): | |
| existing.append(entry) | |
| hooks = settings.get("hooks") | |
| if not isinstance(hooks, dict): | |
| hooks = {} | |
| settings["hooks"] = hooks | |
| snippet = { | |
| "UserPromptSubmit": { | |
| "matcher": "", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": f'python3 "{bridge}" inject --skill claude-code --task "$CLAUDE_USER_PROMPT" --cwd "$PWD"', | |
| } | |
| ], | |
| }, | |
| "Stop": { | |
| "matcher": "", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": f'python3 "{bridge}" run-skill --skill claude-code --task "$CLAUDE_USER_PROMPT" --cwd "$PWD" --output "$(cat)"', | |
| } | |
| ], | |
| }, | |
| } | |
| for event, entry in snippet.items(): | |
| existing = hooks.get(event) | |
| if not isinstance(existing, list): | |
| existing = [] | |
| hooks[event] = existing | |
| command = entry["hooks"][0]["command"] | |
| if not any( | |
| hook.get("command") == command | |
| for item in existing | |
| if isinstance(item, dict) | |
| for hook in item.get("hooks", []) | |
| if isinstance(item.get("hooks", []), list) | |
| ): | |
| existing.append(entry) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/claudecode-skills-memanto/install.sh` around lines 30 - 60, The
installer mutates settings["hooks"] and assumes each event entry is a list of
dicts with a "hooks" list containing dicts with "command", which can crash for
nonstandard shapes; before using hooks = settings.setdefault("hooks", {}),
validate/coerce settings["hooks"] to a dict, and for each event ensure existing
is a list (coerce/replace non-lists), ensure each item in existing is a dict
with a "hooks" list (coerce/replace), and ensure each hook is a dict before
reading hook.get("command"); then perform the duplicate check using these
normalized structures and append entry safely (referencing variables hooks,
snippet, existing, entry, command in the loop).
| ```bash | ||
| cd examples/claudecode-skills-memanto | ||
| python validate.py | ||
| python -m pytest test_skill_memory_bridge.py | ||
| ``` |
There was a problem hiding this comment.
Add dependency install to Quick Start before validation/tests.
Quick Start currently jumps to validate.py/pytest without an explicit install step. Add a pip install -r requirements.txt command to prevent first-run failures.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/claudecode-skills-memanto/README.md` around lines 16 - 20, The Quick
Start in README.md runs validate.py and pytest without installing dependencies;
update the instructions to run pip install -r requirements.txt before invoking
validate.py and python -m pytest test_skill_memory_bridge.py so first-time users
have required packages available (add the pip install step immediately prior to
the validate.py and pytest commands).
| for line in self.path.read_text(encoding="utf-8").splitlines(): | ||
| if not line.strip(): | ||
| continue | ||
| payload = json.loads(line) | ||
| memory = EngineeringMemory( | ||
| content=payload["content"], | ||
| memory_type=payload["memory_type"], | ||
| confidence=float(payload["confidence"]), | ||
| tags=list(payload.get("tags", [])), | ||
| source=payload.get("source", "claude_code_skills"), | ||
| provenance=payload.get("provenance", "inferred"), | ||
| ) | ||
| haystack = tokenize(" ".join([memory.content, *memory.tags])) | ||
| score = len(query_terms & haystack) | ||
| if score: | ||
| scored.append((score, memory)) |
There was a problem hiding this comment.
Malformed JSON lines will crash recall() entirely.
If the JSONL file contains a corrupted or partially-written line (e.g., from a crash during remember()), json.loads(line) raises JSONDecodeError and breaks recall for all memories.
🛡️ Proposed fix to handle malformed lines gracefully
for line in self.path.read_text(encoding="utf-8").splitlines():
if not line.strip():
continue
- payload = json.loads(line)
+ try:
+ payload = json.loads(line)
+ except json.JSONDecodeError:
+ continue # skip corrupted lines
memory = EngineeringMemory(🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/claudecode-skills-memanto/skill_memory_bridge.py` around lines 64 -
79, The recall loop currently calls json.loads(line) which will raise
JSONDecodeError on malformed JSON and abort the whole method; wrap the load in a
try/except (catch json.JSONDecodeError and optionally ValueError) around the
json.loads(line) call inside the for-loop in recall (the block that constructs
EngineeringMemory, tokenizes via tokenize, computes score from query_terms &
haystack, and appends to scored) and on exception log or warn (e.g., mention the
offending line or offset) and continue to the next line so a single bad JSONL
row does not break processing of valid memories.
| subprocess.run( | ||
| [ | ||
| "memanto", | ||
| "remember", | ||
| memory.content, | ||
| "--type", | ||
| memory.memory_type, | ||
| "--confidence", | ||
| str(memory.confidence), | ||
| "--tags", | ||
| ",".join(memory.tags), | ||
| "--source", | ||
| self.source, | ||
| "--provenance", | ||
| memory.provenance, | ||
| ], | ||
| check=True, | ||
| ) | ||
|
|
||
| def recall(self, query: str, limit: int = 5) -> list[EngineeringMemory]: | ||
| result = subprocess.run( | ||
| ["memanto", "recall", query, "--limit", str(limit), "--format", "json"], | ||
| check=True, | ||
| text=True, | ||
| capture_output=True, | ||
| ) |
There was a problem hiding this comment.
Missing timeout on subprocess calls can hang indefinitely.
Both remember() and recall() call subprocess.run(..., check=True) without a timeout. If the memanto CLI hangs (network issues, misconfiguration), the hook will block forever.
⏱️ Proposed fix to add timeouts
subprocess.run(
[
"memanto",
"remember",
# ... args ...
],
check=True,
+ timeout=30,
) result = subprocess.run(
["memanto", "recall", query, "--limit", str(limit), "--format", "json"],
check=True,
text=True,
capture_output=True,
+ timeout=30,
)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| subprocess.run( | |
| [ | |
| "memanto", | |
| "remember", | |
| memory.content, | |
| "--type", | |
| memory.memory_type, | |
| "--confidence", | |
| str(memory.confidence), | |
| "--tags", | |
| ",".join(memory.tags), | |
| "--source", | |
| self.source, | |
| "--provenance", | |
| memory.provenance, | |
| ], | |
| check=True, | |
| ) | |
| def recall(self, query: str, limit: int = 5) -> list[EngineeringMemory]: | |
| result = subprocess.run( | |
| ["memanto", "recall", query, "--limit", str(limit), "--format", "json"], | |
| check=True, | |
| text=True, | |
| capture_output=True, | |
| ) | |
| subprocess.run( | |
| [ | |
| "memanto", | |
| "remember", | |
| memory.content, | |
| "--type", | |
| memory.memory_type, | |
| "--confidence", | |
| str(memory.confidence), | |
| "--tags", | |
| ",".join(memory.tags), | |
| "--source", | |
| self.source, | |
| "--provenance", | |
| memory.provenance, | |
| ], | |
| check=True, | |
| timeout=30, | |
| ) | |
| def recall(self, query: str, limit: int = 5) -> list[EngineeringMemory]: | |
| result = subprocess.run( | |
| ["memanto", "recall", query, "--limit", str(limit), "--format", "json"], | |
| check=True, | |
| text=True, | |
| capture_output=True, | |
| timeout=30, | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/claudecode-skills-memanto/skill_memory_bridge.py` around lines 89 -
114, The subprocess.run calls in remember() and recall() lack a timeout which
can cause the process to hang indefinitely; update both calls to include a
sensible timeout (e.g., a few seconds) and handle subprocess.TimeoutExpired by
catching it where remember() and recall() are defined, logging or re-raising a
controlled error; specifically modify the subprocess.run invocation in the
remember() method and the one in recall() to pass timeout=<seconds> and add
try/except around them to catch subprocess.TimeoutExpired (and optionally
subprocess.CalledProcessError) to ensure the hook fails fast and cleans up
gracefully.
| from pathlib import Path | ||
|
|
||
| settings_path = Path(sys.argv[1]) | ||
| settings = json.loads(settings_path.read_text(encoding="utf-8")) |
There was a problem hiding this comment.
Handle invalid JSON with a clean error message.
Uninstall currently throws a Python traceback on malformed settings.json. A controlled exit keeps CLI behavior consistent with install.
Suggested fix
-settings = json.loads(settings_path.read_text(encoding="utf-8"))
+try:
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
+except json.JSONDecodeError:
+ raise SystemExit(f"Invalid JSON in {settings_path}")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| settings = json.loads(settings_path.read_text(encoding="utf-8")) | |
| try: | |
| settings = json.loads(settings_path.read_text(encoding="utf-8")) | |
| except json.JSONDecodeError: | |
| raise SystemExit(f"Invalid JSON in {settings_path}") |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/claudecode-skills-memanto/uninstall.sh` at line 18, Wrap the
json.loads(settings_path.read_text(encoding="utf-8")) call in a try/except that
catches json.JSONDecodeError, print a clean, user-facing error mentioning the
malformed settings.json (using settings_path to show the file) to stderr, and
exit with a non-zero status (e.g., sys.exit(1)); update the code that assigns
settings so the exception is handled instead of letting a traceback propagate.
| commands = [hook.get("command", "") for hook in entry.get("hooks", [])] | ||
| if any("skill_memory_bridge.py" in command for command in commands): | ||
| continue | ||
| kept.append(entry) |
There was a problem hiding this comment.
Uninstall filter is too broad and can remove unrelated hooks.
Matching only "skill_memory_bridge.py" can delete other user integrations unintentionally. Restrict removal to the exact commands this installer owns.
Suggested fix
for entry in entries:
commands = [hook.get("command", "") for hook in entry.get("hooks", [])]
- if any("skill_memory_bridge.py" in command for command in commands):
+ if any(
+ "skill_memory_bridge.py inject --skill claude-code" in command
+ or "skill_memory_bridge.py run-skill --skill claude-code" in command
+ for command in commands
+ ):
continue
kept.append(entry)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| commands = [hook.get("command", "") for hook in entry.get("hooks", [])] | |
| if any("skill_memory_bridge.py" in command for command in commands): | |
| continue | |
| kept.append(entry) | |
| commands = [hook.get("command", "") for hook in entry.get("hooks", [])] | |
| if any( | |
| "skill_memory_bridge.py inject --skill claude-code" in command | |
| or "skill_memory_bridge.py run-skill --skill claude-code" in command | |
| for command in commands | |
| ): | |
| continue | |
| kept.append(entry) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/claudecode-skills-memanto/uninstall.sh` around lines 24 - 27, The
uninstall filter currently rejects any entry whose hooks contain a command
substring "skill_memory_bridge.py", which is too broad; update the logic in
uninstall.sh where commands = [hook.get("command", "") for hook in
entry.get("hooks", [])] and the subsequent any(...) check to only skip entries
whose hook command exactly matches (or matches one of a small whitelist of) the
installer-owned commands (e.g., full command strings the installer created)
instead of using substring matching — compare command == "exact_owned_command"
or command in {"owned_cmd1", "owned_cmd2"} (or normalize/resolve paths before
comparing) and only then continue; leave kept.append(entry) unchanged.
✅ Actions performedReview triggered.
|
/claim #508
This PR adds
examples/claudecode-skills-memanto, a reviewer-safe Claude CodeSkills x Memanto memory bridge for the BountyHub developer skills challenge.
What it adds
SkillMemoryBridge.before_skill()injects relevant memories as a conciseMEMANTO_SKILL_CONTEXTblock before a skill starts.SkillMemoryBridge.after_skill()extracts durable engineering memories fromskill transcripts after a run completes.
MEMANTO_SKILLS_BACKEND=sdk.MEMANTO_SKILLS_BACKEND=cli./grill-with-docs,/tdd, and/handoff.UserPromptSubmitandStoplifecycle hooks.
.env.example,requirements.txt, andclaude-settings.snippet.jsonreview artifacts.install.sh/uninstall.shfor Claude Code settings integrationwith timestamped backup before mutation.
avoided.
Why this matches the challenge
The example eliminates repeated instructions across skill runs. A decision
captured during
/grill-with-docscan be injected into a later/tddor/handoffsession based on the current skill, task, and working directory.Reviewers can validate the full flow without a Moorcheh API key, then switch to
the live Memanto SDK or CLI backend after configuring their own active agent.
Verification
Installer smoke test:
Showcase
The in-repo showcase is in
examples/claudecode-skills-memanto/demo_transcript.md. It demonstrates aSession A
/grill-with-docsrun storing checkout architecture constraints and aSession B
/tddrun receiving them automatically asMEMANTO_SKILL_CONTEXT.External social showcase: not included in this technical PR. Per the maintainer
comment on #508, external social posts are not mandatory for PR consideration,
but may add points for final judging.
Summary by CodeRabbit
New Features
Documentation
Tests