From 88f07ca36bae6d10415c32033fa1ce90c3a5e323 Mon Sep 17 00:00:00 2001 From: Mladen S Date: Thu, 16 Apr 2026 01:48:09 +0200 Subject: [PATCH 1/3] Fix Claude Code hook installer: use correct settings.json schema Our installer wrote a flat list of {event, command} objects, but Claude Code expects hooks as a dict keyed by event name, with each entry being a list of matcher objects containing a nested hooks array: { "hooks": { "UserPromptSubmit": [ {"hooks": [{"type": "command", "command": "..."}]} ] } } Updates tests to assert the correct schema. --- pretty_please/adapters/claude_code/install.py | 36 ++++++++++++++----- tests/test_cli.py | 7 +++- tests/test_install.py | 9 +++-- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/pretty_please/adapters/claude_code/install.py b/pretty_please/adapters/claude_code/install.py index 0b0abee..453d718 100644 --- a/pretty_please/adapters/claude_code/install.py +++ b/pretty_please/adapters/claude_code/install.py @@ -5,11 +5,25 @@ pretty-please hook. Existing settings are preserved; only the hooks section is merged. +Claude Code hooks schema:: + + { + "hooks": { + "UserPromptSubmit": [ + { + "hooks": [ + {"type": "command", "command": ""} + ] + } + ] + } + } + Usage:: python -m pretty_please.adapters.claude_code.install -Or via the CLI entry point (if installed):: +Or via the CLI entry point:: pretty-please install-hook """ @@ -56,20 +70,26 @@ def install(settings_path: Path | None = None) -> Path: ) return settings_path - hooks: list[dict] = settings.setdefault("hooks", []) + hooks: dict = settings.setdefault("hooks", {}) + user_prompt_hooks: list = hooks.setdefault("UserPromptSubmit", []) - # Check if a pretty-please hook already exists command = _hook_command() - already_installed = any(hook.get("command", "") == command for hook in hooks) + + # Check if a pretty-please hook is already present anywhere in the matchers + already_installed = any( + h.get("command") == command + for matcher in user_prompt_hooks + for h in matcher.get("hooks", []) + ) if not already_installed: - hooks.append( + user_prompt_hooks.append( { - "event": "UserPromptSubmit", - "command": command, + "hooks": [ + {"type": "command", "command": command} + ] } ) - settings["hooks"] = hooks settings_path.write_text(json.dumps(settings, indent=2)) print(f"pretty-please hook installed in {settings_path}") else: diff --git a/tests/test_cli.py b/tests/test_cli.py index 3c2b163..3a3274a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -53,7 +53,12 @@ def test_install_hook_writes_settings(self, tmp_path): path = tmp_path / "settings.json" run("install-hook", "--path", str(path)) data = json.loads(path.read_text()) - assert any(h.get("event") == "UserPromptSubmit" for h in data["hooks"]) + matchers = data["hooks"]["UserPromptSubmit"] + assert any( + h.get("type") == "command" + for matcher in matchers + for h in matcher.get("hooks", []) + ) def test_install_hook_codex_exits_zero(self, tmp_path): path = tmp_path / "hooks.json" diff --git a/tests/test_install.py b/tests/test_install.py index 817a3dd..e4321bd 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -10,7 +10,12 @@ def test_installs_into_empty_dir(self, tmp_path): settings_path = tmp_path / "settings.json" install(settings_path) data = json.loads(settings_path.read_text()) - assert any(h.get("event") == "UserPromptSubmit" for h in data["hooks"]) + matchers = data["hooks"]["UserPromptSubmit"] + assert any( + h.get("type") == "command" + for matcher in matchers + for h in matcher.get("hooks", []) + ) def test_preserves_existing_settings(self, tmp_path): from pretty_please.adapters.claude_code.install import install @@ -28,7 +33,7 @@ def test_idempotent(self, tmp_path): install(settings_path) install(settings_path) data = json.loads(settings_path.read_text()) - assert len(data["hooks"]) == 1 + assert len(data["hooks"]["UserPromptSubmit"]) == 1 def test_bails_on_corrupt_json(self, tmp_path, capsys): from pretty_please.adapters.claude_code.install import install From bdd4e3f983939236c7fa8add078a02af8adca537 Mon Sep 17 00:00:00 2001 From: Mladen S Date: Thu, 16 Apr 2026 01:49:17 +0200 Subject: [PATCH 2/3] Fix ruff formatting in claude_code/install.py --- pretty_please/adapters/claude_code/install.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pretty_please/adapters/claude_code/install.py b/pretty_please/adapters/claude_code/install.py index 453d718..33ac54b 100644 --- a/pretty_please/adapters/claude_code/install.py +++ b/pretty_please/adapters/claude_code/install.py @@ -83,13 +83,7 @@ def install(settings_path: Path | None = None) -> Path: ) if not already_installed: - user_prompt_hooks.append( - { - "hooks": [ - {"type": "command", "command": command} - ] - } - ) + user_prompt_hooks.append({"hooks": [{"type": "command", "command": command}]}) settings_path.write_text(json.dumps(settings, indent=2)) print(f"pretty-please hook installed in {settings_path}") else: From 233a540ec19d536a85d6039ad009162dfd72326c Mon Sep 17 00:00:00 2001 From: Mladen S Date: Thu, 16 Apr 2026 01:50:37 +0200 Subject: [PATCH 3/3] Add pre-commit config for ruff check and format Run once to activate: pip install pre-commit && pre-commit install --- .pre-commit-config.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..855f194 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.0 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format