Skip to content

Single-query (-q) mode sets HERMES_INTERACTIVE=1 but cannot answer approval prompts — agent routes around blocked commands via execute_code #86878

Description

@stig1102

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"):

  1. Tool call blocks on pending_approval, fails with exit -1.
  2. Agent retries 6 command variants (all blocked the same way).
  3. Agent attempts a fake self-approval (echo into the prompt).
  4. 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

  1. 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.
  2. 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).
  3. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existsarea/authAuthentication, OAuth, credential poolscomp/cliCLI entry point, hermes_cli/, setup wizardsweeper:risk-security-boundarySweeper risk: may affect sandboxing, auth, credentials, or sensitive datatool/terminalTerminal execution and process managementtype/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions