Skip to content

Commit 164fb60

Browse files
committed
fix: project detection when Claude doesn't send sessionId
- Fixed hook field name mappings (event, tool, parameters, sessionId) - Added fallback to extract project name from cwd when no session ID - Updated to handle both camelCase and snake_case field names - Version bump to 0.1.11
1 parent 1f09b10 commit 164fb60

4 files changed

Lines changed: 64 additions & 340 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "ccnotify"
3-
version = "0.1.10"
3+
version = "0.1.11"
44
description = "Intelligent notification system for Claude Code with audio feedback"
55
authors = [
66
{name = "Frank Helmschrott", email = "frank@helmschrott.de"}

src/ccnotify/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
CCNotify - Intelligent notification system for Claude Code with audio feedback
33
"""
44

5-
__version__ = "0.1.10"
5+
__version__ = "0.1.11"
66
__author__ = "Helmi"
77
__license__ = "MIT"
88

src/ccnotify/notify.py

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#!/usr/bin/env python3
2+
__version__ = "0.1.10"
3+
24
# /// script
35
# requires-python = ">=3.9"
46
# dependencies = [
@@ -485,7 +487,8 @@ def _get_fallback_sound(self, event_type: str) -> Optional[Path]:
485487

486488
def handle_hook(self, hook_data: Dict[str, Any]):
487489
"""Process hook data and generate appropriate notification"""
488-
hook_type = hook_data.get("hook_event_name", "unknown")
490+
# Claude Code uses "event" field, not "hook_event_name"
491+
hook_type = hook_data.get("event", hook_data.get("hook_event_name", "unknown"))
489492
logger.info(f"Processing hook type: {hook_type}")
490493

491494
# Log all available fields for this hook type
@@ -498,23 +501,43 @@ def handle_hook(self, hook_data: Dict[str, Any]):
498501
# Load replacements configuration
499502
replacements = load_replacements()
500503

501-
# Extract session context
502-
session_id = hook_data.get("session_id", "unknown")
503-
cwd = hook_data.get("cwd", "")
504-
project_name = resolve_project_name(session_id, cwd)
504+
# Extract session context - Claude Code uses camelCase "sessionId"
505+
session_id = hook_data.get("sessionId", hook_data.get("session_id", "unknown"))
506+
cwd = hook_data.get("cwd", os.getcwd()) # Fall back to current working directory
507+
508+
# If no session ID but we have cwd, try to extract project name from path
509+
if session_id == "unknown" and cwd:
510+
# Try to extract project name from cwd
511+
path_parts = Path(cwd).parts
512+
# Look for common project directories
513+
if "code" in path_parts:
514+
idx = path_parts.index("code")
515+
if idx + 1 < len(path_parts):
516+
project_name = path_parts[idx + 1]
517+
else:
518+
project_name = Path(cwd).name
519+
else:
520+
project_name = Path(cwd).name
521+
522+
logger.info(f"No session ID, extracted project from cwd: {project_name}")
523+
else:
524+
project_name = resolve_project_name(session_id, cwd)
525+
logger.info(f"Resolved project name from session: {project_name}")
505526

506527
# Apply project name replacement
507528
display_project_name = apply_project_name_replacement(project_name, replacements)
508529

509530
cwd_name = Path(cwd).name if cwd else "unknown"
510531

511532
if hook_type == "PreToolUse":
512-
tool_name = hook_data.get("tool_name", "unknown")
533+
# Claude Code sends "tool" not "tool_name"
534+
tool_name = hook_data.get("tool", hook_data.get("tool_name", "unknown"))
513535

514536
# Only notify for truly dangerous operations
515537
# Skip notifications for common safe operations
516538
if tool_name == "Bash":
517-
command = hook_data.get("tool_input", {}).get("command", "")
539+
# Claude Code sends "parameters" not "tool_input"
540+
command = hook_data.get("parameters", hook_data.get("tool_input", {})).get("command", "")
518541
# Skip common safe commands
519542
safe_prefixes = ["echo", "pwd", "ls", "cat", "head", "tail", "grep", "find", "which"]
520543
if any(command.strip().startswith(prefix) for prefix in safe_prefixes):
@@ -540,7 +563,8 @@ def handle_hook(self, hook_data: Dict[str, Any]):
540563

541564
# Skip most file edits unless they're system files
542565
elif tool_name in ["Write", "MultiEdit", "Edit"]:
543-
file_path = hook_data.get("tool_input", {}).get("file_path", "")
566+
# Claude Code sends "parameters" not "tool_input"
567+
file_path = hook_data.get("parameters", hook_data.get("tool_input", {})).get("file_path", "")
544568
# Only notify for system/config files
545569
if any(x in file_path for x in ["/etc/", "/usr/", ".env", "config", "secret"]):
546570
event_type = "tool_activity"
@@ -550,8 +574,10 @@ def handle_hook(self, hook_data: Dict[str, Any]):
550574

551575
elif hook_type == "PostToolUse":
552576
# Check for errors in tool response
553-
tool_response = hook_data.get("tool_response", {})
554-
tool_name = hook_data.get("tool_name", "unknown")
577+
# Claude Code might send "response" or "tool_response"
578+
tool_response = hook_data.get("response", hook_data.get("tool_response", {}))
579+
# Claude Code sends "tool" not "tool_name"
580+
tool_name = hook_data.get("tool", hook_data.get("tool_name", "unknown"))
555581

556582
# Debug log the tool response structure
557583
logger.debug(f"PostToolUse response for {tool_name}: {json.dumps(tool_response, indent=2) if isinstance(tool_response, dict) else tool_response}")

0 commit comments

Comments
 (0)