diff --git a/agent/shell_hooks.py b/agent/shell_hooks.py index 5292244e2..c1cec81df 100644 --- a/agent/shell_hooks.py +++ b/agent/shell_hooks.py @@ -224,6 +224,15 @@ def register_from_config( if not isinstance(cfg, dict): return [] + # Safe mode (--safe-mode / HERMES_SAFE_MODE=1): shell hooks are user + # customizations too — skip registration entirely so a troubleshooting + # run fires zero user-configured code (plugins, MCP, AND hooks). + from utils import env_var_enabled + + if env_var_enabled("HERMES_SAFE_MODE"): + logger.info("HERMES_SAFE_MODE=1 — shell-hook registration skipped") + return [] + effective_accept = _resolve_effective_accept(cfg, accept_hooks) specs = _parse_hooks_block(cfg.get("hooks")) diff --git a/tests/hermes_cli/test_safe_mode.py b/tests/hermes_cli/test_safe_mode.py index 6ab0cf533..ffd0b85b8 100644 --- a/tests/hermes_cli/test_safe_mode.py +++ b/tests/hermes_cli/test_safe_mode.py @@ -130,3 +130,40 @@ def test_parser_accepts_safe_mode_on_root_and_chat(): assert parser.parse_args(["--safe-mode"]).safe_mode is True assert parser.parse_args(["chat", "--safe-mode"]).safe_mode is True assert parser.parse_args(["chat"]).safe_mode is False + + +def test_shell_hooks_skipped(monkeypatch): + monkeypatch.setenv("HERMES_SAFE_MODE", "1") + from agent.shell_hooks import register_from_config + + cfg = { + "hooks": { + "pre_tool_call": [{"command": "echo hooked"}], + }, + "hooks_auto_accept": True, + } + + assert register_from_config(cfg, accept_hooks=True) == [] + + +def test_shell_hooks_register_without_safe_mode(monkeypatch): + import agent.shell_hooks as sh + + cfg = { + "hooks": { + "pre_tool_call": [{"command": "echo hooked"}], + }, + "hooks_auto_accept": True, + } + + manager = types.SimpleNamespace(_hooks={}) + plugins = types.ModuleType("hermes_cli.plugins") + setattr(plugins, "get_plugin_manager", lambda: manager) + setattr(plugins, "VALID_HOOKS", {"pre_tool_call"}) + monkeypatch.setitem(sys.modules, "hermes_cli.plugins", plugins) + monkeypatch.setattr(sh, "_registered", set()) + + registered = sh.register_from_config(cfg, accept_hooks=True) + + assert len(registered) == 1 + assert "pre_tool_call" in manager._hooks diff --git a/website/docs/reference/cli-commands.md b/website/docs/reference/cli-commands.md index 1bc4d4b13..b8ace5b19 100644 --- a/website/docs/reference/cli-commands.md +++ b/website/docs/reference/cli-commands.md @@ -117,7 +117,7 @@ Common options: | `--pass-session-id` | Pass the session ID into the system prompt. | | `--ignore-user-config` | Ignore `~/.hermes/config.yaml` and use built-in defaults. Credentials in `.env` are still loaded. Useful for isolated CI runs, reproducible bug reports, and third-party integrations. | | `--ignore-rules` | Skip auto-injection of `AGENTS.md`, `SOUL.md`, `.cursorrules`, persistent memory, and preloaded skills. Combine with `--ignore-user-config` for a fully isolated run. | -| `--safe-mode` | Troubleshooting mode: disable ALL customizations — user config, rules/memory injection, plugins, and MCP servers (implies `--ignore-user-config` and `--ignore-rules`). Use to isolate whether a problem comes from your setup or from Hermes itself. | +| `--safe-mode` | Troubleshooting mode: disable ALL customizations — user config, rules/memory injection, plugins, shell hooks, and MCP servers (implies `--ignore-user-config` and `--ignore-rules`). Use to isolate whether a problem comes from your setup or from Hermes itself. | | `--source ` | Session source tag for filtering (default: `cli`). Use `tool` for third-party integrations that should not appear in user session lists. | | `--max-turns ` | Maximum tool-calling iterations per conversation turn (default: 90, or `agent.max_turns` in config). | diff --git a/website/docs/reference/environment-variables.md b/website/docs/reference/environment-variables.md index 9f4aa2144..9e95d90e3 100644 --- a/website/docs/reference/environment-variables.md +++ b/website/docs/reference/environment-variables.md @@ -716,7 +716,7 @@ Advanced per-platform knobs for throttling the outbound message batcher. Most us | `HERMES_ACCEPT_HOOKS` | Auto-approve any unseen shell hooks declared in `config.yaml` without a TTY prompt. Equivalent to `--accept-hooks` or `hooks_auto_accept: true`. | | `HERMES_IGNORE_USER_CONFIG` | Skip `~/.hermes/config.yaml` and use built-in defaults (credentials in `.env` still load). Equivalent to `--ignore-user-config`. | | `HERMES_IGNORE_RULES` | Skip auto-injection of `AGENTS.md`, `SOUL.md`, `.cursorrules`, memory, and preloaded skills. Equivalent to `--ignore-rules`. | -| `HERMES_SAFE_MODE` | Troubleshooting mode: disable ALL customizations — skips plugin discovery and MCP server loading. Set automatically by `--safe-mode` (which also sets the two flags above). | +| `HERMES_SAFE_MODE` | Troubleshooting mode: disable ALL customizations — skips plugin discovery, MCP server loading, and shell-hook registration. Set automatically by `--safe-mode` (which also sets the two flags above). | | `HERMES_MD_NAMES` | Comma-separated list of rules-file names to auto-inject (default: `AGENTS.md,CLAUDE.md,.cursorrules,SOUL.md`). | | `HERMES_TOOL_PROGRESS` | Deprecated compatibility variable for tool progress display. Prefer `display.tool_progress` in `config.yaml`. | | `HERMES_TOOL_PROGRESS_MODE` | Deprecated compatibility variable for tool progress mode. Prefer `display.tool_progress` in `config.yaml`. | diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/environment-variables.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/environment-variables.md index 7ee79f765..8de0ad1a9 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/environment-variables.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/reference/environment-variables.md @@ -532,7 +532,7 @@ Graph 事件(Teams 会议、日历、聊天等)的入站变更通知监听 | `HERMES_ACCEPT_HOOKS` | 无需 TTY 提示自动批准 `config.yaml` 中声明的任何未见过的 shell hook。等同于 `--accept-hooks` 或 `hooks_auto_accept: true`。 | | `HERMES_IGNORE_USER_CONFIG` | 跳过 `~/.hermes/config.yaml` 并使用内置默认值(`.env` 中的凭证仍会加载)。等同于 `--ignore-user-config`。 | | `HERMES_IGNORE_RULES` | 跳过 `AGENTS.md`、`SOUL.md`、`.cursorrules`、记忆和预加载技能的自动注入。等同于 `--ignore-rules`。 | -| `HERMES_SAFE_MODE` | 故障排查模式:禁用**所有**自定义项——跳过插件发现和 MCP 服务器加载。由 `--safe-mode` 自动设置(同时也会设置上面两个 flag)。 | +| `HERMES_SAFE_MODE` | 故障排查模式:禁用**所有**自定义项——跳过插件发现、MCP 服务器加载和 shell hook 注册。由 `--safe-mode` 自动设置(同时也会设置上面两个 flag)。 | | `HERMES_MD_NAMES` | 自动注入的规则文件名逗号分隔列表(默认:`AGENTS.md,CLAUDE.md,.cursorrules,SOUL.md`)。 | | `HERMES_TOOL_PROGRESS` | 工具进度显示的已弃用兼容变量。优先使用 `config.yaml` 中的 `display.tool_progress`。 | | `HERMES_TOOL_PROGRESS_MODE` | 工具进度模式的已弃用兼容变量。优先使用 `config.yaml` 中的 `display.tool_progress`。 |