From 026b007bb889366283c925527e9a3bab317f66c1 Mon Sep 17 00:00:00 2001 From: Bortlesboat <169967362+Bortlesboat@users.noreply.github.com> Date: Sat, 25 Jul 2026 02:13:02 -0400 Subject: [PATCH] fix(hooks): accept raw control characters in payloads --- CHANGELOG.md | 1 + src/serena/hooks.py | 2 +- test/serena/test_hooks.py | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2a19648..d5e7e093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Status of the `main` branch. Changes prior to the next official version change w - Add `serena-hooks --client=grok`, including Grok-native PreToolUse allow/deny output. - PreToolUse remind hook: coerce non-string shell command values instead of failing, and recognize `target_file`/`targetFile` file-path keys (shared payload parsing, applies to all hook clients). + - Fix hook input parsing for clients that emit raw control characters in JSON string values #1743. # v1.6.1 (2026-07-21) diff --git a/src/serena/hooks.py b/src/serena/hooks.py index 3d53961a..6c2aafa2 100644 --- a/src/serena/hooks.py +++ b/src/serena/hooks.py @@ -31,7 +31,7 @@ class HookClient(Enum): class Hook(ABC): def __init__(self, client: HookClient): raw = sys.stdin.read() - input_data = json.loads(raw) + input_data = json.loads(raw, strict=False) self._input_data = input_data self._client = client diff --git a/test/serena/test_hooks.py b/test/serena/test_hooks.py index 94bd23fb..f0e015b6 100644 --- a/test/serena/test_hooks.py +++ b/test/serena/test_hooks.py @@ -992,6 +992,20 @@ class TestHookCli: output = json.loads(result.output) assert output["hookSpecificOutput"]["permissionDecision"] == "deny" + def test_remind_command_accepts_raw_control_characters_in_tool_input(self, tmp_path: Path): + """CodeBuddy freeform tool input may contain unescaped control characters.""" + stdin_json = ( + '{"hook_event_name":"PreToolUse","session_id":"cli-codebuddy-raw-input",' + '"tool_name":"Edit","tool_input":"*** Begin Patch\n*** Add File: /tmp/x.txt\n+hi\n",' + '"permission_mode":"default"}' + ) + runner = CliRunner() + with patch("serena.hooks.serena_home_dir", str(tmp_path)): + result = runner.invoke(hook_commands, ["remind", "--client", "codebuddy"], input=stdin_json) + + assert result.exit_code == 0 + assert result.output == "" + def test_remind_command_grok_emits_native_deny(self, tmp_path: Path): """The documented Grok remind CLI emits Grok-native deny JSON.""" runner = CliRunner()