mempalace.hook_shell
Source: mempalace/hook_shell.py
Compatibility helpers for legacy shell hooks.
The shell hooks intentionally stay small and portable, but parsing Claude hook JSON and counting UTF-8 JSONL transcripts is safer in Python than in inline shell snippets. This module centralizes that behavior for both hooks/mempal_save_hook.sh and hooks/mempal_precompact_hook.sh.
Functions
sanitize_session_id
def sanitize_session_id(session_id: object) -> strKeep session ids safe for state-file names.
normalize_transcript_path
def normalize_transcript_path(path: object) -> strNormalize a hook transcript path without destroying Windows paths.
Claude Code on Windows sends paths like:
C:\Users\me\.claude\projects\<project>\<session>.jsonl
The old shell sanitizer removed both the drive-letter colon and backslashes. That turned a valid transcript path into a nonexistent path. For transcript paths, we only remove control characters that would break newline-delimited shell parsing, and normalize backslashes to forward slashes so Git Bash can still address the same Windows file.
parse_stop_payload
def parse_stop_payload(payload: dict) -> tuple[str, str, str]parse_precompact_payload
def parse_precompact_payload(payload: dict) -> tuple[str, str]count_human_messages
def count_human_messages(path: str) -> intCount user messages in a Claude transcript JSONL file.
Claude transcripts are UTF-8. Windows Python defaults to cp1252 in many environments, so the encoding must be explicit. Invalid bytes are ignored to match the hooks' fail-soft behavior.
A path that exists but is not a regular file counts zero rather than being opened: opening a FIFO for reading blocks in the kernel until a writer appears, and this function has no timeout. A path that does not exist still raises from the open below, as before. mempal_save_hook.sh screens with [ -f ], which is false for a pipe, so the guard here covers callers that do not.
main
def main(argv: list[str] | None = None) -> int