test: cover Claude Code install lifecycle hooks#237
Conversation
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
📝 Walkthrough
WalkthroughClaude Code integration now wires hook commands across five lifecycle stages—PreToolUse, PostToolUse, Stop, PreCompact, and UserPromptSubmit—using new shared command helpers. The adapter uses signature-based detection to avoid duplicates and supports idempotent re-installation. Comprehensive snapshot and behavioral tests validate the configuration output, preservation of user hooks, and presence of all required lifecycles. ChangesClaude Code multi-lifecycle hook wiring
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labelsfeature 🚥 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 docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 OpenGrep (1.22.0)OpenGrep fatal error (exit code 2): �[32m✔�[39m �[1mOpengrep OSS�[0m �[1m Loading rules from local config...�[0m Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@Gradata/src/gradata/hooks/adapters/claude_code.py`:
- Around line 91-103: The PostToolUse matcher currently only checks "Edit|Write"
so MultiEdit events are missed; update the matcher value used when adding to
post_tool in the has_post_tool branch (the dict with keys "matcher", "hooks") to
include MultiEdit (e.g. "Edit|Write|MultiEdit") so PostToolUse events from
MultiEdit trigger the auto_correct_command(brain_dir) hook associated with id
sig; leave the rest of the structure (hooks list, command via
auto_correct_command, id sig) unchanged.
In `@Gradata/tests/test_install_claude_code_snapshot.py`:
- Around line 87-105: The snapshot normalizer _normalized_snapshot currently
replaces BRAIN_DIR and hook signature IDs but leaves absolute Python interpreter
paths in serialized, causing env-specific snapshot failures; update
_normalized_snapshot to also replace absolute interpreter/executable paths found
in serialized (e.g., occurrences ending in "python" or "python<digits or dot>"
and common Windows backslash paths) with a stable placeholder like
"__PYTHON_EXECUTABLE__" (add a re.sub on the existing serialized string, similar
to the existing BRAIN_DIR and hook-signature replacements, so snapshots no
longer vary by interpreter path).
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 0eed7c4a-4ab8-418d-92fa-ca079f3b0c39
📒 Files selected for processing (4)
Gradata/src/gradata/hooks/adapters/_base.pyGradata/src/gradata/hooks/adapters/claude_code.pyGradata/tests/snapshots/install_claude_code_settings.jsonGradata/tests/test_install_claude_code_snapshot.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: pytest macos-latest / py3.11
- GitHub Check: pytest ubuntu-latest / py3.12
- GitHub Check: pytest macos-latest / py3.12
- GitHub Check: pytest windows-latest / py3.11
- GitHub Check: pytest ubuntu-latest / py3.11
- GitHub Check: pytest windows-latest / py3.12
- GitHub Check: pytest (py3.11)
- GitHub Check: pytest (py3.12)
🧰 Additional context used
📓 Path-based instructions (2)
Gradata/src/**/*.py
📄 CodeRabbit inference engine (Gradata/AGENTS.md)
Gradata/src/**/*.py: Prefersentence-transformersfor local embeddings,google-genaifor Gemini embeddings,cryptographyfor AES-GCM encrypted system.db,bm25sfor BM25 rule ranking, andmem0aifor external memory adapters — guard all optional dependency imports withtry / except ImportErrorat the call site, never at module level
Maintain strict layering: Layer 0 (Primitives: _types.py, _db.py, _events.py, _paths.py, _file_lock.py; Patterns: contrib/patterns/) must never import from Layer 1 (Enhancements: enhancements/, rules/) or Layer 2 (Public API: brain.py, cli.py, daemon.py, mcp_server.py)
Never use bareexcept: pass— use typed exceptions or at minimumlogger.warning(...)withexc_info=Trueto avoid silent failure in a memory product
Never import from out-of-scope sibling directories../Sprites/or../Hausgem/withingradata/*code — that is a layering bug
Never leak private-sibling paths into public docs/code — no references to../Sprites/,../Hausgem/, email addresses, OneDrive paths, or Sprites-specific examples from insidegradata/*
Use atomic-write helper when writing JSON files to prevent corruption from mid-write crashes
Files:
Gradata/src/gradata/hooks/adapters/_base.pyGradata/src/gradata/hooks/adapters/claude_code.py
Gradata/tests/**/*.py
📄 CodeRabbit inference engine (Gradata/AGENTS.md)
Gradata/tests/**/*.py: SetBRAIN_DIRenvironment variable viatmp_pathin conftest.py for test isolation — ensure_paths.pymodule cache refreshes when callingBrain.init()directly inside tests
Add unit tests intests/test_*.pyfor every CI push without LLM calls (deterministic); mark integration tests with@pytest.mark.integrationand skip them by default (they hit real LLM APIs)
Files:
Gradata/tests/test_install_claude_code_snapshot.py
🔇 Additional comments (4)
Gradata/src/gradata/hooks/adapters/_base.py (1)
139-164: LGTM!Gradata/src/gradata/hooks/adapters/claude_code.py (1)
10-18: LGTM!Also applies to: 166-189
Gradata/tests/snapshots/install_claude_code_settings.json (1)
1-62: LGTM!Gradata/tests/test_install_claude_code_snapshot.py (1)
21-84: LGTM!Also applies to: 108-277
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@Gradata/tests/test_install_claude_code_snapshot.py`:
- Around line 97-114: Update the three re.sub normalizers operating on the
variable serialized to also match Windows backslash paths and python.exe forms:
(1) change the BRAIN_DIR pattern used in the first re.sub to accept either
forward slash or backslash as the path separator (e.g. use a character class
like [\\/]) so it normalizes both "/brain" and "\brain"; (2) update the hook
signature pattern in the second re.sub to allow backslashes before brain
(replace the literal / in '"gradata:claude-code:(?:/|[A-Za-z]:\\\\)[^"]+brain"'
with a separator that accepts / or \); and (3) broaden the interpreter regex in
the third re.sub to permit backslashes and optional ".exe" on the python
executable (e.g. match python(?:\.exe)? and numeric suffixes like
python3.9(?:\.exe)?), ensuring you keep the same capture groups (the prefix
"(BRAIN_DIR=__BRAIN_DIR__ )" and suffix "( -m gradata\.hooks\.)") so the
replacement r"\1__PYTHON_EXECUTABLE__\2" continues to work.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: af7ed18f-2a2e-405c-b4b0-35d82728c0a6
📒 Files selected for processing (3)
Gradata/src/gradata/hooks/adapters/claude_code.pyGradata/tests/snapshots/install_claude_code_settings.jsonGradata/tests/test_install_claude_code_snapshot.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: pytest macos-latest / py3.12
- GitHub Check: pytest windows-latest / py3.11
- GitHub Check: pytest windows-latest / py3.12
- GitHub Check: pytest ubuntu-latest / py3.12
- GitHub Check: pytest macos-latest / py3.11
🧰 Additional context used
📓 Path-based instructions (2)
Gradata/src/**/*.py
📄 CodeRabbit inference engine (Gradata/AGENTS.md)
Gradata/src/**/*.py: Prefersentence-transformersfor local embeddings,google-genaifor Gemini embeddings,cryptographyfor AES-GCM encrypted system.db,bm25sfor BM25 rule ranking, andmem0aifor external memory adapters — guard all optional dependency imports withtry / except ImportErrorat the call site, never at module level
Maintain strict layering: Layer 0 (Primitives: _types.py, _db.py, _events.py, _paths.py, _file_lock.py; Patterns: contrib/patterns/) must never import from Layer 1 (Enhancements: enhancements/, rules/) or Layer 2 (Public API: brain.py, cli.py, daemon.py, mcp_server.py)
Never use bareexcept: pass— use typed exceptions or at minimumlogger.warning(...)withexc_info=Trueto avoid silent failure in a memory product
Never import from out-of-scope sibling directories../Sprites/or../Hausgem/withingradata/*code — that is a layering bug
Never leak private-sibling paths into public docs/code — no references to../Sprites/,../Hausgem/, email addresses, OneDrive paths, or Sprites-specific examples from insidegradata/*
Use atomic-write helper when writing JSON files to prevent corruption from mid-write crashes
Files:
Gradata/src/gradata/hooks/adapters/claude_code.py
Gradata/tests/**/*.py
📄 CodeRabbit inference engine (Gradata/AGENTS.md)
Gradata/tests/**/*.py: SetBRAIN_DIRenvironment variable viatmp_pathin conftest.py for test isolation — ensure_paths.pymodule cache refreshes when callingBrain.init()directly inside tests
Add unit tests intests/test_*.pyfor every CI push without LLM calls (deterministic); mark integration tests with@pytest.mark.integrationand skip them by default (they hit real LLM APIs)
Files:
Gradata/tests/test_install_claude_code_snapshot.py
🔇 Additional comments (2)
Gradata/src/gradata/hooks/adapters/claude_code.py (1)
94-94: LGTM!Gradata/tests/snapshots/install_claude_code_settings.json (1)
7-7: LGTM!Also applies to: 12-12, 19-19, 31-31, 43-43, 54-54
| # Normalize: BRAIN_DIR=<platform temp path>/brain → BRAIN_DIR=__BRAIN_DIR__ | ||
| serialized = re.sub( | ||
| r"BRAIN_DIR=(?:/|[A-Za-z]:\\\\)[^\"]*?/brain", | ||
| "BRAIN_DIR=__BRAIN_DIR__", | ||
| serialized, | ||
| ) | ||
| # Normalize: hook signature ID | ||
| serialized = re.sub( | ||
| r'"gradata:claude-code:(?:/|[A-Za-z]:\\\\)[^"]+brain"', | ||
| '"gradata:claude-code:__BRAIN_DIR__"', | ||
| serialized, | ||
| ) | ||
| # Normalize: interpreter path in command strings. | ||
| serialized = re.sub( | ||
| r"(BRAIN_DIR=__BRAIN_DIR__ )\S*(?:python|python(?:\d+(?:\.\d+)?))( -m gradata\.hooks\.)", | ||
| r"\1__PYTHON_EXECUTABLE__\2", | ||
| serialized, | ||
| ) |
There was a problem hiding this comment.
Broaden snapshot normalization to cover Windows path/executable forms.
The new normalizer still misses common Windows forms (\brain and python.exe), so snapshot output can remain environment-dependent.
Proposed fix
- serialized = re.sub(
- r"BRAIN_DIR=(?:/|[A-Za-z]:\\\\)[^\"]*?/brain",
+ serialized = re.sub(
+ r"BRAIN_DIR=(?:/|[A-Za-z]:\\\\)[^\"]*(?:/|\\\\)+brain",
"BRAIN_DIR=__BRAIN_DIR__",
serialized,
)
@@
- serialized = re.sub(
- r"(BRAIN_DIR=__BRAIN_DIR__ )\S*(?:python|python(?:\d+(?:\.\d+)?))( -m gradata\.hooks\.)",
+ serialized = re.sub(
+ r"(BRAIN_DIR=__BRAIN_DIR__ )\S*python(?:\d+(?:\.\d+)?)?(?:\.exe)?( -m gradata\.hooks\.)",
r"\1__PYTHON_EXECUTABLE__\2",
serialized,
)🤖 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 `@Gradata/tests/test_install_claude_code_snapshot.py` around lines 97 - 114,
Update the three re.sub normalizers operating on the variable serialized to also
match Windows backslash paths and python.exe forms: (1) change the BRAIN_DIR
pattern used in the first re.sub to accept either forward slash or backslash as
the path separator (e.g. use a character class like [\\/]) so it normalizes both
"/brain" and "\brain"; (2) update the hook signature pattern in the second
re.sub to allow backslashes before brain (replace the literal / in
'"gradata:claude-code:(?:/|[A-Za-z]:\\\\)[^"]+brain"' with a separator that
accepts / or \); and (3) broaden the interpreter regex in the third re.sub to
permit backslashes and optional ".exe" on the python executable (e.g. match
python(?:\.exe)? and numeric suffixes like python3.9(?:\.exe)?), ensuring you
keep the same capture groups (the prefix "(BRAIN_DIR=__BRAIN_DIR__ )" and suffix
"( -m gradata\.hooks\.)") so the replacement r"\1__PYTHON_EXECUTABLE__\2"
continues to work.
Summary
gradata install --agent claude-codesettings output and idempotence/preservation behaviorTest plan
python3 -m pytest Gradata/tests/test_install_claude_code_snapshot.py -q-> 4 passedpython3 -m pytest Gradata/tests/test_hook_adapters.py Gradata/tests/test_install_claude_code_snapshot.py -q-> 12 passedCloses GRA-1211
Unblocks GRA-1198 / GH #206