Bug Description
The CLI's in-chat /yolo command toggles os.environ["HERMES_YOLO_MODE"] but has no effect because tools/approval.py:_YOLO_MODE_FROZEN is captured at module-import time. By the time the user reaches the /yolo prompt in a running CLI session, tools.approval has already been imported (it's pulled in transitively by the terminal tool / tool registry at CLI startup), so flipping the env var afterward never changes the frozen flag.
The /yolo CLI handler also does not call enable_session_yolo() the way the gateway and TUI handlers do, so the per-session bypass path can't fire either.
Net effect: /yolo toggles the status-bar "⚠ YOLO" badge ON but every dangerous command still hits an approval prompt or is denied. The only working full-bypass routes are hermes --yolo, HERMES_YOLO_MODE=1 hermes ..., and hermes config set approvals.mode off — all of which are evaluated before module import (the env-var routes) or read fresh from config on every call (the config route).
Code Pointers
The freeze (intentional, security hardening):
tools/approval.py:26-29
# Freeze YOLO mode at module import time. Reading os.environ on every call
# would allow any skill running inside the process to set this variable and
# instantly bypass all approval checks — a prompt-injection escalation path.
_YOLO_MODE_FROZEN: bool = is_truthy_value(os.getenv("HERMES_YOLO_MODE", ""))
The broken CLI handler (env-var-only, no enable_session_yolo call):
cli.py:9610-9627
def _toggle_yolo(self):
"""Toggle YOLO mode — skip all dangerous command approval prompts."""
import os
from hermes_cli.colors import Colors as _Colors
current = is_truthy_value(os.environ.get("HERMES_YOLO_MODE"))
if current:
os.environ.pop("HERMES_YOLO_MODE", None)
_cprint(
f" ⚠ YOLO mode {_Colors.BOLD}{_Colors.RED}OFF{_Colors.RESET}"
" — dangerous commands will require approval."
)
else:
os.environ["HERMES_YOLO_MODE"] = "1"
_cprint(
f" ⚡ YOLO mode {_Colors.BOLD}{_Colors.GREEN}ON{_Colors.RESET}"
" — all commands auto-approved. Use with caution."
)
For comparison, the gateway handler does it correctly:
gateway/run.py:12074-12089
async def _handle_yolo_command(self, event: MessageEvent) -> Union[str, EphemeralReply]:
from tools.approval import (
disable_session_yolo, enable_session_yolo, is_session_yolo_enabled,
)
session_key = self._session_key_for_source(event.source)
current = is_session_yolo_enabled(session_key)
if current:
disable_session_yolo(session_key)
return EphemeralReply(t("gateway.yolo.disabled"))
else:
enable_session_yolo(session_key)
return EphemeralReply(t("gateway.yolo.enabled"))
tui_gateway/server.py:4244-4267 does the same enable_session_yolo(session["session_key"]) thing on the TUI path.
The actual check that gates terminal commands:
tools/approval.py:1086-1090
# --yolo or approvals.mode=off: bypass all approval prompts.
# Gateway /yolo is session-scoped; CLI --yolo remains process-scoped.
approval_mode = _get_approval_mode()
if _YOLO_MODE_FROZEN or is_current_session_yolo_enabled() or approval_mode == "off":
return {"approved": True, "message": None}
For the CLI /yolo handler, none of the three conditions are true after the toggle:
_YOLO_MODE_FROZEN was captured at module import (before /yolo ran)
is_current_session_yolo_enabled() returns False because enable_session_yolo was never called
approval_mode == "off" requires the persisted config setting
Repro
$ hermes
> /yolo
⚡ YOLO mode ON — all commands auto-approved. Use with caution.
> run `rm -rf /tmp/foo` to clean up
⚠ This command is potentially dangerous (...). Asking for approval...
The status bar reads ⚠ YOLO but every dangerous command is still gated.
Expected Behavior
/yolo from inside the CLI should behave identically to:
hermes --yolo at startup
/yolo from inside the TUI
/yolo from a gateway platform (telegram, discord, etc.)
All four should toggle approval bypass for the active session, and the change should be visible to check_all_command_guards on the very next dangerous command.
Proposed Fix
The CLI /yolo handler should call enable_session_yolo(session_key) / disable_session_yolo(session_key) instead of mutating os.environ["HERMES_YOLO_MODE"]. This requires:
- The CLI agent run path to bind a per-session approval key via
set_current_session_key() so is_current_session_yolo_enabled() resolves against the same key as the toggle stored under. (The gateway and TUI already do this; the interactive CLI does not.)
_toggle_yolo() to read/write the session-yolo set instead of the env var.
- The status-bar
yolo_active = bool(os.getenv("HERMES_YOLO_MODE")) check (cli.py:3750, cli.py:3811) updated to read from the session-yolo helper too, so the badge reflects reality.
Keep the env-var path working at process start (it's how --yolo propagates to subprocesses and skills), but stop treating runtime env mutation as a YOLO toggle source.
Related
Environment
- Hermes Agent v0.14.0 (2026.5.16) [a1eaad2]
- Reproduced via code inspection on
origin/main at 0554ef1
- Reporter confirmed repro on macOS Darwin 24.6.0 + Ubuntu server
- Provider/model not relevant — this is a local terminal approval-layer bug
Bug Description
The CLI's in-chat
/yolocommand togglesos.environ["HERMES_YOLO_MODE"]but has no effect becausetools/approval.py:_YOLO_MODE_FROZENis captured at module-import time. By the time the user reaches the/yoloprompt in a running CLI session,tools.approvalhas already been imported (it's pulled in transitively by the terminal tool / tool registry at CLI startup), so flipping the env var afterward never changes the frozen flag.The
/yoloCLI handler also does not callenable_session_yolo()the way the gateway and TUI handlers do, so the per-session bypass path can't fire either.Net effect:
/yolotoggles the status-bar "⚠ YOLO" badge ON but every dangerous command still hits an approval prompt or is denied. The only working full-bypass routes arehermes --yolo,HERMES_YOLO_MODE=1 hermes ..., andhermes config set approvals.mode off— all of which are evaluated before module import (the env-var routes) or read fresh from config on every call (the config route).Code Pointers
The freeze (intentional, security hardening):
tools/approval.py:26-29The broken CLI handler (env-var-only, no
enable_session_yolocall):cli.py:9610-9627For comparison, the gateway handler does it correctly:
gateway/run.py:12074-12089tui_gateway/server.py:4244-4267does the sameenable_session_yolo(session["session_key"])thing on the TUI path.The actual check that gates terminal commands:
tools/approval.py:1086-1090For the CLI
/yolohandler, none of the three conditions are true after the toggle:_YOLO_MODE_FROZENwas captured at module import (before/yoloran)is_current_session_yolo_enabled()returns False becauseenable_session_yolowas never calledapproval_mode == "off"requires the persisted config settingRepro
The status bar reads
⚠ YOLObut every dangerous command is still gated.Expected Behavior
/yolofrom inside the CLI should behave identically to:hermes --yoloat startup/yolofrom inside the TUI/yolofrom a gateway platform (telegram, discord, etc.)All four should toggle approval bypass for the active session, and the change should be visible to
check_all_command_guardson the very next dangerous command.Proposed Fix
The CLI
/yolohandler should callenable_session_yolo(session_key)/disable_session_yolo(session_key)instead of mutatingos.environ["HERMES_YOLO_MODE"]. This requires:set_current_session_key()sois_current_session_yolo_enabled()resolves against the same key as the toggle stored under. (The gateway and TUI already do this; the interactive CLI does not.)_toggle_yolo()to read/write the session-yolo set instead of the env var.yolo_active = bool(os.getenv("HERMES_YOLO_MODE"))check (cli.py:3750,cli.py:3811) updated to read from the session-yolo helper too, so the badge reflects reality.Keep the env-var path working at process start (it's how
--yolopropagates to subprocesses and skills), but stop treating runtime env mutation as a YOLO toggle source.Related
approvals.modedropdown shows stale["ask", "yolo", "deny"]instead of["manual", "smart", "off"]. Same family of bug (UI surface that doesn't actually bypass approvals).HERMES_YOLO_MODE=falseas YOLO on.computer_usetool; different scope, doesn't touch the CLI/yolopath.Environment
origin/mainat 0554ef1