{"repo":"NousResearch/hermes-agent","public_id":"4261","display_label":"approvals.mode validation, defaults, and UI fixes","summary":"These issues and pull requests address invalid approval mode values being accepted silently, incorrect or stale dropdown options in the dashboard UI, and fixes to validation, defaulting, and guard logic around the approvals.mode configuration.","subsystem":"agent","counts":{"members":12,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":3,"merged_prs":3,"attempt_prs":6},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":54469},"resolution":{"state":"partially_resolved","by":"pr-63271"},"freshness":{"state":"verified","last_verified_at":"2026-07-13T00:42:42+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.16242424242424242},"fix_edges":[{"issue_number":4261,"pr_number":54469,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":4261,"pr_number":62661,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":31925,"pr_number":63271,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":4261,"title":"Bug: approvals.mode accepts invalid values silently (e.g. 'auto')","url":null,"state":"closed"},{"kind":"issue","number":31925,"title":"Dashboard UI: approvals.mode missing 'smart' option, shows stale option names","url":null,"state":"closed"},{"kind":"issue","number":44821,"title":"Dashboard `approvals.mode` dropdown options don't match actual engine values","url":null,"state":"closed"},{"kind":"pr","number":4304,"title":"fix(core): validate approval mode and safely default to smart (#4261)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#54469","number":54469},{"kind":"pr","value":"#62661","number":62661}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":4682,"title":"fix: /approve and /deny bypass running-agent guard + warn on unknown approval mode","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"dda3268d0","number":0},{"kind":"pr","value":"#54469","number":54469}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":20816,"title":"fix(approval): validate approval mode against known values, warn on invalid","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#54469","number":54469},{"kind":"pr","value":"#62661","number":62661}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":31933,"title":"fix(dashboard): correct approvals.mode select options","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#63271","number":63271}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":44820,"title":"fix(dashboard): correct approvals.mode dropdown options","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#31933","number":31933},{"kind":"pr","value":"#31925","number":31925}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":54469,"title":"fix(approvals): warn and default to manual on unknown approvals.mode","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["infographic/approval-mode-validation/infographic.png","scripts/release.py","tests/tools/test_approval.py","tools/approval.py"],"fix_hunks":[{"file":"scripts/release.py","hunks":["@@ -46,6 +46,7 @@\n # Auto-extracted from noreply emails + manual overrides\n AUTHOR_MAP = {\n     \"[redacted:email]\": \"Kolektori\",  # PR #6436 salvage (require approval for host-bound Docker commands; container guard fast-path)\n+    \"[redacted:email]\": \"LIC99\",  # PR #4682 salvage (warn + default to manual on unknown approvals.mode; #4261)\n     \"[redacted:email]\": \"cmcejas\",  # PR #41188 salvage (early Telegram auth gate before event build/observe; #40863)\n     \"[redacted:email]\": \"oreoluwa\",  # PR #49845 salvage (skip preflight content-type probe for OAuth MCP servers so OAuth discovery runs; Akiflow/Hospitable)\n     \"[redacted:email]\": \"PRATHAMESH75\",  # PR #37550 salvage (ExecStopPost cgroup-orphan reaper to unblock systemd restart; #37454)"]},{"file":"tests/tools/test_approval.py","hunks":["@@ -12,6 +12,7 @@\n from hermes_constants import get_hermes_home\n from tools.approval import (\n     _get_approval_mode,\n+    _normalize_approval_mode,\n     _smart_approve,\n     approve_session,\n     detect_dangerous_command,","@@ -30,6 +31,27 @@ def test_string_off_still_maps_to_off(self):\n         with mock_patch(\"hermes_cli.config.load_config\", return_value={\"approvals\": {\"mode\": \"off\"}}):\n             assert _get_approval_mode() == \"off\"\n \n+    def test_valid_modes_pass_through(self):\n+        assert _normalize_approval_mode(\"manual\") == \"manual\"\n+        assert _normalize_approval_mode(\"smart\") == \"smart\"\n+        assert _normalize_approval_mode(\"off\") == \"off\"\n+\n+    def test_valid_mode_is_case_insensitive_and_trimmed(self):\n+        assert _normalize_approval_mode(\"  SMART  \") == \"smart\"\n+\n+    def test_unknown_mode_defaults_to_manual_with_warning(self):\n+        with mock_patch.object(approval_module.logger, \"warning\") as warn:\n+            assert _normalize_approval_mode(\"auto\") == \"manual\"\n+            warn.assert_called_once()\n+\n+    def test_empty_string_defaults_to_manual_without_warning(self):\n+        with mock_patch.object(approval_module.logger, \"warning\") as warn:\n+            assert _normalize_approval_mode(\"\") == \"manual\"\n+            warn.assert_not_called()\n+\n+    def test_yaml_bool_true_maps_to_manual(self):\n+        assert _normalize_approval_mode(True) == \"manual\"\n+\n \n class TestSmartApproval:\n     def test_smart_approval_uses_call_llm(self):"]},{"file":"tools/approval.py","hunks":["@@ -1097,12 +1097,27 @@ def _normalize_approval_mode(mode) -> str:\n     YAML 1.1 treats bare words like `off` as booleans, so a config entry like\n     `approvals:\\n  mode: off` is parsed as False unless quoted. Treat that as the\n     intended string mode instead of falling back to manual approvals.\n+\n+    Unknown string values (e.g. 'auto') are rejected with a warning rather than\n+    being silently accepted and falling through every mode check downstream.\n+    Always returns one of 'manual', 'smart', or 'off'.\n     \"\"\"\n+    _VALID_MODES = (\"manual\", \"smart\", \"off\")\n     if isinstance(mode, bool):\n         return \"off\" if mode is False else \"manual\"\n     if isinstance(mode, str):\n         normalized = mode.strip().lower()\n-        return normalized or \"manual\"\n+        if not normalized:\n+            return \"manual\"\n+        if normalized in _VALID_MODES:\n+            return normalized\n+        logger.warning(\n+            \"Unknown approvals.mode %r — defaulting to 'manual'. \"\n+            \"Valid values: %s\",\n+            mode,\n+            \", \".join(_VALID_MODES),\n+        )\n+        return \"manual\"\n     return \"manual\"\n \n \n"]}]},{"kind":"pr","number":59391,"title":"fix: correct dashboard approvals.mode dropdown options to match valid…","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#31933","number":31933},{"kind":"pr","value":"#31933","number":31933},{"kind":"pr","value":"#31925","number":31925},{"kind":"pr","value":"#44820","number":44820},{"kind":"pr","value":"#44821","number":44821},{"kind":"pr","value":"#31925","number":31925}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":62661,"title":"feat: make smart approvals the default","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/config.py","skills/autonomous-ai-agents/hermes-agent/SKILL.md","tests/tools/test_approval.py","tools/approval.py","website/docs/user-guide/configuration.md","website/docs/user-guide/security.md","website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md","website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/configuration.md","website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/security.md","website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md"],"fix_hunks":[{"file":"hermes_cli/config.py","hunks":["@@ -2523,15 +2523,15 @@ def _ensure_hermes_home_managed(home: Path):\n     },\n \n     # Approval mode for dangerous commands:\n-    #   manual — always prompt the user (default)\n-    #   smart  — use auxiliary LLM to auto-approve low-risk commands, prompt for high-risk\n+    #   manual — always prompt the user\n+    #   smart  — use auxiliary LLM to auto-approve low-risk commands (default)\n     #   off    — skip all approval prompts (equivalent to --yolo)\n     #\n     # cron_mode — what to do when a cron job hits a dangerous command:\n     #   deny    — block the command and let the agent find another way (default, safe)\n     #   approve — auto-approve all dangerous commands in cron jobs\n     \"approvals\": {\n-        \"mode\": \"manual\",\n+        \"mode\": \"smart\",\n         \"timeout\": 60,\n         \"cron_mode\": \"deny\",\n         # User-defined deny rules: fnmatch globs matched against terminal"]},{"file":"skills/autonomous-ai-agents/hermes-agent/SKILL.md","hunks":["@@ -552,10 +552,10 @@ hermes config set privacy.redact_pii false   # disable (default)\n \n ### Command approval prompts\n \n-By default (`approvals.mode: manual`), Hermes prompts the user before running shell commands flagged as destructive (`rm -rf`, `git reset --hard`, etc.). The modes are:\n+By default (`approvals.mode: smart`), Hermes asks an auxiliary LLM to assess shell commands flagged as destructive (`rm -rf`, `git reset --hard`, etc.). The modes are:\n \n-- `manual` — always prompt (default)\n-- `smart` — use an auxiliary LLM to auto-approve low-risk commands, prompt on high-risk\n+- `smart` — auto-approve a low-risk command once, deny high-risk commands, and prompt when uncertain (default)\n+- `manual` — always prompt\n - `off` — skip all approval prompts (equivalent to `--yolo`)\n \n ```bash"]},{"file":"tests/tools/test_approval.py","hunks":["@@ -55,6 +55,11 @@ def test_yaml_bool_true_maps_to_manual(self):\n \n \n class TestSmartApproval:\n+    def test_smart_is_the_default_approval_mode(self):\n+        from hermes_cli.config import DEFAULT_CONFIG\n+\n+        assert DEFAULT_CONFIG[\"approvals\"][\"mode\"] == \"smart\"\n+\n     def test_smart_approval_uses_call_llm(self):\n         response = SimpleNamespace(\n             choices=[SimpleNamespace(message=SimpleNamespace(content=\"APPROVE\"))]","@@ -68,6 +73,35 @@ def test_smart_approval_uses_call_llm(self):\n         assert mock_call.call_args.kwargs[\"temperature\"] == 0\n         assert mock_call.call_args.kwargs[\"max_tokens\"] == 16\n \n+    def test_smart_approval_does_not_allowlist_the_pattern_for_session(self, monkeypatch):\n+        session_key = \"test-smart-per-command\"\n+        command = \"python -c \\\"print('hello')\\\"\"\n+        dangerous, pattern_key, _ = detect_dangerous_command(command)\n+        assert dangerous is True\n+\n+        monkeypatch.setenv(\"HERMES_SESSION_KEY\", session_key)\n+        monkeypatch.setenv(\"HERMES_EXEC_ASK\", \"1\")\n+        monkeypatch.delenv(\"HERMES_CRON_SESSION\", raising=False)\n+        monkeypatch.setattr(\n+            approval_module,\n+            \"_get_approval_config\",\n+            lambda: {\"mode\": \"smart\"},\n+        )\n+        monkeypatch.setattr(approval_module, \"_YOLO_MODE_FROZEN\", False)\n+        monkeypatch.setattr(approval_module, \"_smart_approve\", lambda *_: \"approve\")\n+        monkeypatch.setattr(\n+            \"tools.tirith_security.check_command_security\",\n+            lambda _command: {\"action\": \"allow\", \"findings\": [], \"summary\": \"\"},\n+        )\n+        approval_module.clear_session(session_key)\n+        approval_module._permanent_approved.clear()\n+\n+        result = approval_module.check_all_command_guards(command, \"local\")\n+\n+        assert result[\"approved\"] is True\n+        assert result[\"smart_approved\"] is True\n+        assert is_approved(session_key, pattern_key) is False\n+\n \n class TestDetectDangerousRm:\n     def test_rm_rf_detected(self):","@@ -2140,11 +2174,13 @@ def test_timeout_message_is_emphatic_against_retry_and_rephrase(self, monkeypatc\n         assert \"rephrase\" in msg.lower()\n         assert \"different command\" in msg.lower()\n \n-    def test_explicit_deny_carries_same_no_consent_shape(self):\n+    def test_explicit_deny_carries_same_no_consent_shape(self, monkeypatch):\n         \"\"\"An explicit /deny must produce the same shape as timeout —\n         the agent should treat both identically.\"\"\"\n         from tools import approval as mod\n \n+        self._force_short_timeout(monkeypatch, seconds=60)\n+\n         notified = []\n         mod.register_gateway_notify(self.SESSION_KEY, lambda data: notified.append(data))\n "]},{"file":"tools/approval.py","hunks":["@@ -2745,9 +2745,9 @@ def check_all_command_guards(command: str, env_type: str,\n         combined_desc_for_llm = \"; \".join(desc for _, desc, _ in warnings)\n         verdict = _smart_approve(command, combined_desc_for_llm)\n         if verdict == \"approve\":\n-            # Auto-approve and grant session-level approval for these patterns\n-            for key, _, _ in warnings:\n-                approve_session(session_key, key)\n+            # Approve this command only. Pattern-level persistence would let one\n+            # benign command suppress review of later commands that happen to\n+            # match the same broad detector category.\n             logger.debug(\"Smart approval: auto-approved '%s' (%s)\",\n                          command[:60], combined_desc_for_llm)\n             return {\"approved\": True, \"message\": None,"]},{"file":"website/docs/user-guide/configuration.md","hunks":["@@ -1879,13 +1879,13 @@ Control how Hermes handles potentially dangerous commands:\n \n ```yaml\n approvals:\n-  mode: manual   # manual | smart | off\n+  mode: smart   # smart | manual | off\n ```\n \n | Mode | Behavior |\n |------|----------|\n-| `manual` (default) | Prompt the user before executing any flagged command. In the CLI, shows an interactive approval dialog. In messaging, queues a pending approval request. |\n-| `smart` | Use an auxiliary LLM to assess whether a flagged command is actually dangerous. Low-risk commands are auto-approved with session-level persistence. Genuinely risky commands are escalated to the user. |\n+| `smart` (default) | Use an auxiliary LLM to assess whether a flagged command is actually dangerous. Low-risk commands are auto-approved for that command only. Genuinely risky commands are denied; uncertain decisions escalate to the user. |\n+| `manual` | Prompt the user before executing any flagged command. In the CLI, shows an interactive approval dialog. In messaging, queues a pending approval request. |\n | `off` | Skip all approval checks. Equivalent to `HERMES_YOLO_MODE=true`. **Use with caution.** |\n \n Smart mode is particularly useful for reducing approval fatigue — it lets the agent work more autonomously on safe operations while still catching genuinely destructive commands."]},{"file":"website/docs/user-guide/security.md","hunks":["@@ -30,7 +30,7 @@ The approval system supports three modes, configured via `approvals.mode` in `~/\n \n ```yaml\n approvals:\n-  mode: manual                    # manual | smart | off\n+  mode: smart                     # smart | manual | off\n   timeout: 60                     # seconds to wait for user response (default: 60)\n   cron_mode: deny                 # deny | approve — what cron jobs do when they hit a dangerous command\n   mcp_reload_confirm: true        # /reload-mcp asks before invalidating the MCP tool cache","@@ -41,7 +41,7 @@ The full set of keys:\n \n | Key | Default | What it controls |\n |---|---|---|\n-| `mode` | `manual` | Approval policy for dangerous shell commands — see the table below. |\n+| `mode` | `smart` | Approval policy for dangerous shell commands — see the table below. |\n | `timeout` | `60` | Seconds Hermes waits for an approval reply before timing out. |\n | `cron_mode` | `deny` | How [cron jobs](./features/cron.md) behave headlessly when they trigger a dangerous-command prompt. `deny` blocks the command (the agent must find another path); `approve` auto-approves everything in cron context. |\n | `mcp_reload_confirm` | `true` | When true, `/reload-mcp` asks before rebuilding the MCP tool set. Rebuilding invalidates the provider prompt cache (tool schemas live in the system prompt), so the next message re-sends full input tokens. Users who click **Always Approve** flip this key to `false`. |","@@ -49,8 +49,8 @@ The full set of keys:\n \n | Mode | Behavior |\n |------|----------|\n-| **manual** (default) | Always prompt the user for approval on dangerous commands |\n-| **smart** | Use an auxiliary LLM to assess risk. Low-risk commands (e.g., `python -c \"print('hello')\"`) are auto-approved. Genuinely dangerous commands are auto-denied. Uncertain cases escalate to a manual prompt. |\n+| **smart** (default) | Use an auxiliary LLM to assess risk. Low-risk commands (e.g., `python -c \"print('hello')\"`) are auto-approved for that command only. Genuinely dangerous commands are auto-denied. Uncertain cases escalate to a manual prompt. |\n+| **manual** | Always prompt the user for approval on dangerous commands. |\n | **off** | Disable all approval checks — equivalent to running with `--yolo`. All commands execute without prompts. |\n \n :::warning"]},{"file":"website/docs/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md","hunks":["@@ -492,10 +492,10 @@ hermes config set privacy.redact_pii false   # disable (default)\n \n ### Command approval prompts\n \n-By default (`approvals.mode: manual`), Hermes prompts the user before running shell commands flagged as destructive (`rm -rf`, `git reset --hard`, etc.). The modes are:\n+By default (`approvals.mode: smart`), Hermes asks an auxiliary LLM to assess shell commands flagged as destructive (`rm -rf`, `git reset --hard`, etc.). The modes are:\n \n-- `manual` — always prompt (default)\n-- `smart` — use an auxiliary LLM to auto-approve low-risk commands, prompt on high-risk\n+- `smart` — auto-approve a low-risk command once, deny high-risk commands, and prompt when uncertain (default)\n+- `manual` — always prompt\n - `off` — skip all approval prompts (equivalent to `--yolo`)\n \n ```bash"]},{"file":"website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/configuration.md","hunks":["@@ -1583,13 +1583,13 @@ security:\n \n ```yaml\n approvals:\n-  mode: manual   # manual | smart | off\n+  mode: smart   # smart | manual | off\n ```\n \n | 模式 | 行为 |\n |------|----------|\n-| `manual`（默认） | 在执行任何被标记的命令之前提示用户。在 CLI 中显示交互式审批对话框。在消息中排队待处理的审批请求。 |\n-| `smart` | 使用辅助 LLM 评估被标记的命令是否真正危险。低风险命令以会话级持久性自动批准。真正有风险的命令升级给用户。 |\n+| `smart`（默认） | 使用辅助 LLM 评估被标记的命令是否真正危险。低风险命令仅对当前命令自动批准，真正危险的命令自动拒绝，不确定的情况升级给用户。 |\n+| `manual` | 在执行任何被标记的命令之前提示用户。在 CLI 中显示交互式审批对话框。在消息中排队待处理的审批请求。 |\n | `off` | 跳过所有审批检查。等同于 `HERMES_YOLO_MODE=true`。**谨慎使用。** |\n \n 智能模式对于减少审批疲劳特别有用 —— 它让 agent 在安全操作上更自主地工作，同时仍然捕获真正破坏性的命令。"]},{"file":"website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/security.md","hunks":["@@ -30,14 +30,14 @@ Hermes Agent 采用纵深防御安全模型。本页涵盖所有安全边界—\n \n ```yaml\n approvals:\n-  mode: manual    # manual | smart | off\n+  mode: smart     # smart | manual | off\n   timeout: 60     # 等待用户响应的秒数（默认：60）\n ```\n \n | 模式 | 行为 |\n |------|----------|\n-| **manual**（默认） | 始终提示用户审批危险命令 |\n-| **smart** | 使用辅助 LLM 评估风险。低风险命令（如 `python -c \"print('hello')\"` ）自动批准，真正危险的命令自动拒绝，不确定的情况升级为手动提示。 |\n+| **smart**（默认） | 使用辅助 LLM 评估风险。低风险命令（如 `python -c \"print('hello')\"`）仅对当前命令自动批准，真正危险的命令自动拒绝，不确定的情况升级为手动提示。 |\n+| **manual** | 始终提示用户审批危险命令。 |\n | **off** | 禁用所有审批检查——等同于使用 `--yolo` 运行。所有命令无需提示即可执行。 |\n \n :::warning"]},{"file":"website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/skills/bundled/autonomous-ai-agents/autonomous-ai-agents-hermes-agent.md","hunks":["@@ -481,10 +481,10 @@ hermes config set privacy.redact_pii false   # 禁用（默认）\n \n ### 命令审批提示\n \n-默认情况下（`approvals.mode: manual`），Hermes 在运行被标记为破坏性的 shell 命令（`rm -rf`、`git reset --hard` 等）之前会提示用户。模式如下：\n+默认情况下（`approvals.mode: smart`），Hermes 会让辅助 LLM 评估被标记为破坏性的 shell 命令（`rm -rf`、`git reset --hard` 等）。模式如下：\n \n-- `manual` — 始终提示（默认）\n-- `smart` — 使用辅助 LLM 自动批准低风险命令，对高风险命令提示\n+- `smart` — 低风险命令仅批准一次，高风险命令拒绝，不确定时提示（默认）\n+- `manual` — 始终提示\n - `off` — 跳过所有审批提示（等同于 `--yolo`）\n \n ```bash\n"]}]},{"kind":"pr","number":63271,"title":"fix(dashboard): align approval mode options","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/web_server.py","scripts/release.py","tests/hermes_cli/test_web_server.py","website/docs/user-guide/features/web-dashboard.md","website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/web-dashboard.md"],"fix_hunks":[{"file":"hermes_cli/web_server.py","hunks":["@@ -671,7 +671,7 @@ async def _token_auth_seam(request: Request, call_next):\n     \"approvals.mode\": {\n         \"type\": \"select\",\n         \"description\": \"Dangerous command approval mode\",\n-        \"options\": [\"ask\", \"yolo\", \"deny\"],\n+        \"options\": [\"manual\", \"smart\", \"off\"],\n     },\n     \"context.engine\": {\n         \"type\": \"select\","]},{"file":"scripts/release.py","hunks":["@@ -46,6 +46,7 @@\n # Auto-extracted from noreply emails + manual overrides\n AUTHOR_MAP = {\n     \"[redacted:email]\": \"ansel-f\",  # PR #62388 salvage (approval: allow exact verifier temp cleanup without broadening rm safety boundary)\n+    \"[redacted:email]\": \"Hopfensaft\",  # PR #31933 salvage (dashboard: align approvals.mode dropdown with canonical engine values)\n     \"[redacted:email]\": \"professorpalmer\",  # PR #48591 salvage (sessions: CLI workspace filter + restore-cwd-on-resume)\n     \"[redacted:email]\": \"Supersynergy\",  # PR #59241 salvage (desktop: workspace path status-bar action)\n     \"[redacted:email]\": \"esthonjr\",  # PR #61950 salvage (desktop: legacy non-git workspace grouping + Windows path identity)"]},{"file":"tests/hermes_cli/test_web_server.py","hunks":["@@ -3539,6 +3539,26 @@ def test_overrides_applied(self):\n             assert \"options\" in entry\n             assert \"local\" in entry[\"options\"]\n \n+    def test_approvals_mode_options_match_config_values(self):\n+        \"\"\"approvals.mode select options must match the values accepted by config.py.\n+\n+        Previously the dashboard showed ['ask', 'yolo', 'deny'] which are stale\n+        names that don't correspond to any real config value. The correct values\n+        are 'manual', 'smart', and 'off' (see hermes_cli/config.py).\n+        'smart' was missing entirely, making it unreachable from the UI.\n+        \"\"\"\n+        from hermes_cli.web_server import CONFIG_SCHEMA\n+        entry = CONFIG_SCHEMA[\"approvals.mode\"]\n+        assert entry[\"type\"] == \"select\"\n+        options = entry[\"options\"]\n+        assert \"manual\" in options, \"'manual' missing from approvals.mode options\"\n+        assert \"smart\" in options, \"'smart' missing from approvals.mode options\"\n+        assert \"off\" in options, \"'off' missing from approvals.mode options\"\n+        # Stale names that were previously shown but don't match config values\n+        assert \"ask\" not in options, \"stale option 'ask' should not appear\"\n+        assert \"yolo\" not in options, \"stale option 'yolo' should not appear\"\n+        assert \"deny\" not in options, \"stale option 'deny' should not appear\"\n+\n     def test_empty_prefix_produces_correct_keys(self):\n         from hermes_cli.web_server import _build_schema_from_config\n         test_config = {\"model\": \"test\", \"nested\": {\"key\": \"val\"}}"]},{"file":"website/docs/user-guide/features/web-dashboard.md","hunks":["@@ -194,7 +194,7 @@ A form-based editor for `config.yaml`. All 150+ configuration fields are auto-di\n - **agent** — max iterations, gateway timeout, service tier\n - **delegation** — subagent limits, reasoning effort\n - **memory** — provider selection, context injection settings\n-- **approvals** — dangerous command approval mode (ask/yolo/deny)\n+- **approvals** — dangerous command approval mode (smart/manual/off)\n - And more — every section of config.yaml has corresponding form fields\n \n Fields with known valid values (terminal backend, skin, approval mode, etc.) render as dropdowns. Booleans render as toggles. Everything else is a text input."]},{"file":"website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/web-dashboard.md","hunks":["@@ -95,7 +95,7 @@ Chat 标签页是每次 `hermes dashboard` 启动的一部分——内嵌的浏\n - **agent** — 最大迭代次数、gateway 超时、服务层级\n - **delegation** — 子 agent 限制、推理力度\n - **memory** — 提供商选择、上下文注入设置\n-- **approvals** — 危险命令审批模式（ask/yolo/deny）\n+- **approvals** — 危险命令审批模式（smart/manual/off）\n - 更多——config.yaml 的每个部分都有对应的表单字段\n \n 具有已知有效值的字段（terminal 后端、皮肤、审批模式等）渲染为下拉菜单。布尔值渲染为开关。其余均为文本输入框。\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}