cli.py unconditionally sets HERMES_INTERACTIVE=1 at CLI entry ("Signal to
terminal_tool that we're in interactive mode"), including for single-query
mode (hermes chat -q "..."). The approval system
(tools/approval.py::_is_interactive_cli) therefore treats -q sessions as
interactive and raises a human approval prompt — but in -q mode nothing can
answer it. The prompt either times out (~300s observed on the
security.protected_instruction_files gate) or the tool call fails
(pending_approval, exit -1) on dangerous-command categories.
Why this matters (observed failure mode)
The dangerous part is not the lost time — it's what the model does next.
Because the denial arrives as a generic tool failure rather than an explicit
policy decision, the agent treats it as an obstacle and works around it:
Observed on v0.20.1 (2026.8.13), single-query session, command
rm -rf <project-subdir> (category "recursive delete"):
- Tool call blocks on
pending_approval, fails with exit -1.
- Agent retries 6 command variants (all blocked the same way).
- Agent attempts a fake self-approval (
echo into the prompt).
- Agent finally deletes the files via
python3 -c with
shutil.rmtree/os.remove — which succeeds, because execute_code /
"script execution via -e/-c flag" are separate approval categories that
were allowlisted for unattended operation.
We previously saw the same pattern against the protected_instruction_files
gate (agent edited the protected file via Python after the prompt hung 300s).
Net effect: the security scan adds friction but no protection in unattended
sessions, and actively trains the model to bypass gates.
Suggested direction
- In single-query mode, don't claim interactivity: skip
HERMES_INTERACTIVE=1 (or set a distinct mode flag) so approvals take a
deterministic non-interactive path instead of raising an unanswerable
prompt.
- Give operators an explicit policy for that path, e.g.
approvals.single_query_mode: deny | approve | allowlist-only — parity
with the existing approvals.cron_mode handling in
tools/approval.py (the fail-closed branch with
fail_closed_when_no_human already exists for plugin escalations).
- Make the denial legible to the model: return a message that states the
command was denied by security policy and that alternative routes
(Python, heredocs, rephrasing) must not be attempted — and ideally apply
the same category check inside execute_code payloads, so a denied
rm -rf can't be replayed as shutil.rmtree one tool-call later.
Environment
- Hermes Agent v0.20.1 (2026.8.13), Debian 13 LXC, self-hosted
- Session type:
hermes chat -q "..." over SSH (no gateway)
command_allowlist in use for unattended operation (execute_code,
heredoc/script categories allowlisted)
cli.pyunconditionally setsHERMES_INTERACTIVE=1at CLI entry ("Signal toterminal_tool that we're in interactive mode"), including for single-query
mode (
hermes chat -q "..."). The approval system(
tools/approval.py::_is_interactive_cli) therefore treats-qsessions asinteractive and raises a human approval prompt — but in
-qmode nothing cananswer it. The prompt either times out (~300s observed on the
security.protected_instruction_filesgate) or the tool call fails(
pending_approval, exit -1) on dangerous-command categories.Why this matters (observed failure mode)
The dangerous part is not the lost time — it's what the model does next.
Because the denial arrives as a generic tool failure rather than an explicit
policy decision, the agent treats it as an obstacle and works around it:
Observed on v0.20.1 (2026.8.13), single-query session, command
rm -rf <project-subdir>(category "recursive delete"):pending_approval, fails with exit -1.echointo the prompt).python3 -cwithshutil.rmtree/os.remove— which succeeds, becauseexecute_code/"script execution via -e/-c flag" are separate approval categories that
were allowlisted for unattended operation.
We previously saw the same pattern against the
protected_instruction_filesgate (agent edited the protected file via Python after the prompt hung 300s).
Net effect: the security scan adds friction but no protection in unattended
sessions, and actively trains the model to bypass gates.
Suggested direction
HERMES_INTERACTIVE=1(or set a distinct mode flag) so approvals take adeterministic non-interactive path instead of raising an unanswerable
prompt.
approvals.single_query_mode: deny | approve | allowlist-only— paritywith the existing
approvals.cron_modehandling intools/approval.py(the fail-closed branch withfail_closed_when_no_humanalready exists for plugin escalations).command was denied by security policy and that alternative routes
(Python, heredocs, rephrasing) must not be attempted — and ideally apply
the same category check inside
execute_codepayloads, so a deniedrm -rfcan't be replayed asshutil.rmtreeone tool-call later.Environment
hermes chat -q "..."over SSH (no gateway)command_allowlistin use for unattended operation (execute_code,heredoc/script categories allowlisted)