mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-14 08:18:36 +00:00
fix(hooks): detect grep/read shell commands in Codex exec_command (fixes #1394)
When the Codex client sends shell work via `exec_command`, the tool name is always `exec_command` and the actual command is under `tool_input.cmd`. The previous `is_grep_tool` / `is_read_file_tool` checks inspected only the tool name and therefore never fired for Codex grep/read workflows. Add `_GREP_SHELL_COMMANDS` and `_READ_SHELL_COMMANDS` frozensets and a helper `_get_exec_command_name()` that extracts the basename of the first token in `cmd`. Both detection methods now use these to classify `exec_command` calls when the client is `HookClient.CODEX`, leaving all other client paths unchanged. Co-Authored-By: Octopus <liyuan851277048@icloud.com>
This commit is contained in:
1 parent
0a96f71bd9
commit
84e117c5c2
2 files changed
+34
-1
No files matched your search
@@ -2,6 +2,9 @@
|
||||
|
||||
Status of the `main` branch. Changes prior to the next official version change will appear here.
|
||||
|
||||
* Hooks:
|
||||
- Extension: heuristics for parsing commands and firing a hook on too many greps or reads. Important for clients that, unlike claude code, don't have dedicated grep/read tools.
|
||||
|
||||
# v1.2.0 (2026-04-27)
|
||||
|
||||
* General:
|
||||
|
||||
+31
-1
@@ -241,20 +241,50 @@ class PreToolUseRemindAboutSerenaHook(PreToolUseHook):
|
||||
#: ingest. Conservative on purpose: only verbs that strongly imply *reading*
|
||||
#: a file, never *modifying* one — so ``view_file``/``open_file``/``show_file``
|
||||
#: are caught alongside ``read_file``, while ``write_file``/``edit_file`` are not.
|
||||
_READ_FILE_VERB_SUBSTRINGS: tuple[str, ...] = ("read", "view", "open", "show")
|
||||
_READ_FILE_VERB_SUBSTRINGS: frozenset[str] = frozenset(("read", "view", "open", "show"))
|
||||
|
||||
#: Shell commands that perform grep-like search; used to classify Codex
|
||||
#: ``exec_command`` calls whose ``cmd`` field starts with one of these.
|
||||
_GREP_SHELL_COMMANDS: frozenset[str] = frozenset(("grep", "rg", "ag", "ack", "fgrep", "egrep"))
|
||||
|
||||
#: Shell commands that perform file-read operations; used to classify Codex
|
||||
#: ``exec_command`` calls whose ``cmd`` field starts with one of these.
|
||||
_READ_SHELL_COMMANDS: frozenset[str] = frozenset(("cat", "head", "tail", "sed", "less", "more", "bat"))
|
||||
|
||||
def __init__(self, client: HookClient):
|
||||
super().__init__(client)
|
||||
self._tool_call_counter = self.ToolUseCounter.load(self)
|
||||
|
||||
def _get_exec_command_name(self) -> str:
|
||||
"""Extract the command name from an ``exec_command`` tool input.
|
||||
|
||||
Returns the basename of the first whitespace-separated token of the
|
||||
``cmd`` field, lowercased, so that both bare names (``rg``) and
|
||||
path-prefixed invocations (``/usr/bin/grep``) are normalised the same
|
||||
way. Returns an empty string when the input is absent or empty.
|
||||
"""
|
||||
if self._tool_input is None:
|
||||
return ""
|
||||
cmd = str(self._tool_input.get("cmd", "") or "").strip()
|
||||
if not cmd:
|
||||
return ""
|
||||
first_token = cmd.split()[0]
|
||||
return os.path.basename(first_token).lower()
|
||||
|
||||
def is_grep_tool(self) -> bool:
|
||||
if self._client == HookClient.CLAUDE_CODE:
|
||||
return self._tool_name == "grep"
|
||||
if self._client == HookClient.CODEX and self._tool_name == "exec_command":
|
||||
return self._get_exec_command_name() in self._GREP_SHELL_COMMANDS
|
||||
# heuristic for other clients
|
||||
return "grep" in self._tool_name
|
||||
|
||||
def is_read_file_tool(self) -> bool:
|
||||
if self._client == HookClient.CLAUDE_CODE:
|
||||
return self._tool_name == "read"
|
||||
if self._client == HookClient.CODEX and self._tool_name == "exec_command":
|
||||
return self._get_exec_command_name() in self._READ_SHELL_COMMANDS
|
||||
# heuristic for other clients
|
||||
name = self._tool_name
|
||||
if "file" not in name:
|
||||
return False
|
||||
|
||||
Reference in new issue
Block a user