From fdd29b570a3460a02a6da82ec34e03e7e85b2793 Mon Sep 17 00:00:00 2001 From: zerone0x Date: Wed, 3 Jun 2026 04:48:58 +0200 Subject: [PATCH] fix: emit valid codex stop-hook cleanup output Fixes #1533 Co-Authored-By: Claude --- src/serena/hooks.py | 16 +++++++++++++++- test/serena/test_hooks.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/serena/hooks.py b/src/serena/hooks.py index ef5e21067..05e7a22ff 100644 --- a/src/serena/hooks.py +++ b/src/serena/hooks.py @@ -516,8 +516,22 @@ def execute(self) -> None: class SessionEndCleanupHook(Hook): + def __init__(self, client: HookClient): + raw = sys.stdin.read() + input_data = json.loads(raw) if raw else {} + self._input_data = input_data + self._client = client + + session_id = input_data.get("session_id") or input_data.get("sessionId") + self._session_id = str(session_id) if session_id else None + self.session_persistence_dir = os.path.join(serena_home_dir, "hook_data", self._session_id) if self._session_id is not None else "" + self.triggered_at_timestamp = datetime.now() + def execute(self) -> None: - shutil.rmtree(self.session_persistence_dir, ignore_errors=True) + if self._session_id is not None: + shutil.rmtree(self.session_persistence_dir, ignore_errors=True) + if self._client == HookClient.CODEX: + click.echo(json.dumps({"continue": True})) class PreToolUseAutoApproveSerenaHook(PreToolUseHook): diff --git a/test/serena/test_hooks.py b/test/serena/test_hooks.py index e762c1302..244a2cacd 100644 --- a/test/serena/test_hooks.py +++ b/test/serena/test_hooks.py @@ -677,6 +677,24 @@ def test_cleanup_is_idempotent(self, tmp_path: Path): with patch("sys.stdin", _make_stdin(stdin_data)), patch("serena.hooks.serena_home_dir", str(tmp_path)): SessionEndCleanupHook(HookClient.CLAUDE_CODE).execute() + def test_codex_cleanup_emits_continue_json(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]): + session_dir = tmp_path / "hook_data" / "codex-cleanup" + session_dir.mkdir(parents=True) + (session_dir / "tool_use_counter.pkl").write_bytes(pickle.dumps(ToolUseCounter())) + + stdin_data = {"session_id": "codex-cleanup"} + with patch("sys.stdin", _make_stdin(stdin_data)), patch("serena.hooks.serena_home_dir", str(tmp_path)): + SessionEndCleanupHook(HookClient.CODEX).execute() + + assert not session_dir.exists() + assert json.loads(capsys.readouterr().out.strip()) == {"continue": True} + + def test_codex_cleanup_without_session_id_is_noop(self, tmp_path: Path, capsys: pytest.CaptureFixture[str]): + with patch("sys.stdin", _make_stdin({})), patch("serena.hooks.serena_home_dir", str(tmp_path)): + SessionEndCleanupHook(HookClient.CODEX).execute() + + assert json.loads(capsys.readouterr().out.strip()) == {"continue": True} + class TestHookCli: """Tests for the Click CLI entry point (serena-hooks).""" @@ -693,6 +711,19 @@ def test_cleanup_command(self, tmp_path: Path): assert result.exit_code == 0 assert not session_dir.exists() + def test_cleanup_command_codex(self, tmp_path: Path): + session_dir = tmp_path / "hook_data" / "cli-cleanup-codex" + session_dir.mkdir(parents=True) + (session_dir / "somefile").write_text("data") + + stdin_json = json.dumps({"session_id": "cli-cleanup-codex"}) + runner = CliRunner() + with patch("serena.hooks.serena_home_dir", str(tmp_path)): + result = runner.invoke(hook_commands, ["cleanup", "--client", "codex"], input=stdin_json) + assert result.exit_code == 0 + assert not session_dir.exists() + assert json.loads(result.output) == {"continue": True} + def test_remind_command(self, tmp_path: Path): """Invoke the remind command enough times to trigger a deny.""" runner = CliRunner()