Skip to content

Add Claude Code skills Memanto bridge#549

Open
adad44 wants to merge 1 commit into
moorcheh-ai:mainfrom
adad44:adad44/claudecode-skills-memanto-508
Open

Add Claude Code skills Memanto bridge#549
adad44 wants to merge 1 commit into
moorcheh-ai:mainfrom
adad44:adad44/claudecode-skills-memanto-508

Conversation

@adad44
Copy link
Copy Markdown

@adad44 adad44 commented May 22, 2026

/claim #508

This PR adds examples/claudecode-skills-memanto, a reviewer-safe Claude Code
Skills x Memanto memory bridge for the BountyHub developer skills challenge.

What it adds

  • SkillMemoryBridge.before_skill() injects relevant memories as a concise
    MEMANTO_SKILL_CONTEXT block before a skill starts.
  • SkillMemoryBridge.after_skill() extracts durable engineering memories from
    skill transcripts after a run completes.
  • Credential-free local JSONL backend for maintainers and reviewers.
  • Optional direct Memanto SDK backend via MEMANTO_SKILLS_BACKEND=sdk.
  • Optional live Memanto CLI backend via MEMANTO_SKILLS_BACKEND=cli.
  • Wrapper generation for /grill-with-docs, /tdd, and /handoff.
  • Claude Code settings snippet generation for UserPromptSubmit and Stop
    lifecycle hooks.
  • Checked-in .env.example, requirements.txt, and
    claude-settings.snippet.json review artifacts.
  • Idempotent install.sh / uninstall.sh for Claude Code settings integration
    with timestamped backup before mutation.
  • Secret and prompt-injection filtering before memories are persisted.
  • Repeated-instruction benchmark that reports how many manual restatements were
    avoided.
  • README, reproducible demo transcript, validator, and focused pytest coverage.

Why this matches the challenge

The example eliminates repeated instructions across skill runs. A decision
captured during /grill-with-docs can be injected into a later /tdd or
/handoff session 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

python3 examples/claudecode-skills-memanto/validate.py
# credential_free_validation=passed

python3 examples/claudecode-skills-memanto/skill_memory_bridge.py benchmark
# {
#   "baseline_repeated_instructions": 3,
#   "memanto_recovered_instructions": 3,
#   "manual_repetition_avoided": 3,
#   "status": "passed"
# }

uvx pytest examples/claudecode-skills-memanto/test_skill_memory_bridge.py -q
# 9 passed

uvx ruff check examples/claudecode-skills-memanto
# All checks passed

Installer smoke test:

tmp=$(mktemp -d)
CLAUDE_CONFIG_DIR="$tmp/.claude" examples/claudecode-skills-memanto/install.sh
CLAUDE_CONFIG_DIR="$tmp/.claude" examples/claudecode-skills-memanto/install.sh
CLAUDE_CONFIG_DIR="$tmp/.claude" examples/claudecode-skills-memanto/uninstall.sh
# second install is idempotent; uninstall leaves {}

Showcase

The in-repo showcase is in
examples/claudecode-skills-memanto/demo_transcript.md. It demonstrates a
Session A /grill-with-docs run storing checkout architecture constraints and a
Session B /tdd run receiving them automatically as MEMANTO_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

    • Added new example project integrating Claude Code skills with memory persistence and context injection across sessions.
  • Documentation

    • Added README with quick-start guide, setup instructions, and workflow documentation.
    • Added demo transcript showcasing memory retention across multiple sessions.
    • Added Claude settings snippet configuration template.
  • Tests

    • Added comprehensive test suite covering memory extraction, storage, and integration workflows.
    • Added validation script for testing core functionality.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

📝 Walkthrough

Walkthrough

This 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.

Changes

Memanto Skills Bridge Implementation

Layer / File(s) Summary
Configuration and dependencies
examples/claudecode-skills-memanto/.env.example, requirements.txt
Environment variables for backend selection and shared settings; runtime dependencies on memanto, pytest, and ruff with version constraints.
Memory model and backend protocol
examples/claudecode-skills-memanto/skill_memory_bridge.py (lines 1–116)
EngineeringMemory dataclass defines typed memory with fields for content, type, confidence, tags, and source; MemoryBackend protocol declares remember() and recall() methods; regex patterns detect structured memory segments and filter secrets/injection attempts.
Storage backend implementations
examples/claudecode-skills-memanto/skill_memory_bridge.py (lines 118–166)
Three MemoryBackend implementations: JsonlMemoryBackend using JSONL file persistence and token-based recall scoring, MemantoCliBackend via subprocess calls and parsing, and MemantoSdkBackend using SDK initialization with API key/session/agent management.
Memory extraction and bridge orchestration
examples/claudecode-skills-memanto/skill_memory_bridge.py (lines 167–275)
SkillMemoryBridge orchestrates before-skill context injection and after-skill memory extraction; extract_engineering_memories() uses regex patterns to parse decisions/preferences/instructions/constraints/learning from transcripts with confidence scoring and tag generation; deduplication compares normalized content; formatting produces MEMANTO_SKILL_CONTEXT: environment blocks.
Helper utilities and backend selection
examples/claudecode-skills-memanto/skill_memory_bridge.py (lines 277–402)
Text utilities tokenize, normalize, and sanitize tags; build_backend() selects active backend (Memanto SDK/CLI or JSONL) based on environment variables, with JSONL default under home directory.
CLI commands and installation automation
examples/claudecode-skills-memanto/skill_memory_bridge.py (lines 296–456)
Main CLI entrypoint dispatches inject, run-skill, install-wrappers, write-settings, and benchmark subcommands; wrapper generation creates per-skill shell scripts; write_claude_settings_snippet() outputs JSON wiring the script into Claude UserPromptSubmit and Stop hooks; benchmark measures instruction deduplication over repeated scenarios.
Installation, uninstallation, and validation
examples/claudecode-skills-memanto/install.sh, uninstall.sh, validate.py, demo_transcript.md, claude-settings.snippet.json
install.sh backs up and modifies ~/.claude/settings.json to inject hook entries without duplication; uninstall.sh removes hooks and cleans empty structures; validate.py runs credential-free end-to-end tests using temporary JSONL store and fixed skill scenarios; demo_transcript.md demonstrates two-session memory persistence; claude-settings.snippet.json provides manual hook configuration template.
Documentation and README
examples/claudecode-skills-memanto/README.md
Comprehensive guide covering quick start (validation, pytest), offline demo (run-skill, inject), backend modes (CLI vs SDK), wrapper generation, live Memanto configuration, feature mapping, and multi-session showcase demonstrating memory persistence across invocations.
Tests and integration validation
examples/claudecode-skills-memanto/test_skill_memory_bridge.py
Pytest suite validating transcript-to-memory extraction with type/confidence/tag verification, JSONL backend persistence and recall, bridge context injection and deduplication, wrapper installation with executable permissions, secret/injection filtering, Claude settings generation, benchmark output, and context formatting.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A bridge built strong 'tween skill and mind,
Memories of wisdom, carefully signed,
Extract. Store. Inject. Repeat.
Engineering notes make context sweet,
Skills shall remember, decisions aligned! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main addition: a bridge between Claude Code skills and Memanto for memory management, which is the primary focus of all files added in this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
examples/claudecode-skills-memanto/skill_memory_bridge.py (1)

138-147: ⚡ Quick win

Broad exception catch with string matching is fragile.

Catching all Exception types 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:
                 raise

Alternatively, check if the Memanto SDK exposes a specific exception type (e.g., AgentExistsError or ConflictError) 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

📥 Commits

Reviewing files that changed from the base of the PR and between a3cbf8a and f0e7254.

📒 Files selected for processing (10)
  • examples/claudecode-skills-memanto/.env.example
  • examples/claudecode-skills-memanto/README.md
  • examples/claudecode-skills-memanto/claude-settings.snippet.json
  • examples/claudecode-skills-memanto/demo_transcript.md
  • examples/claudecode-skills-memanto/install.sh
  • examples/claudecode-skills-memanto/requirements.txt
  • examples/claudecode-skills-memanto/skill_memory_bridge.py
  • examples/claudecode-skills-memanto/test_skill_memory_bridge.py
  • examples/claudecode-skills-memanto/uninstall.sh
  • examples/claudecode-skills-memanto/validate.py

Comment on lines +9 to +20
"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)\""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
"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.

Comment on lines +30 to +60
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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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).

Comment on lines +16 to +20
```bash
cd examples/claudecode-skills-memanto
python validate.py
python -m pytest test_skill_memory_bridge.py
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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).

Comment on lines +64 to +79
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))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Comment on lines +89 to +114
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,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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"))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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.

Comment on lines +24 to +27
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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant