hermes - ✅(Solved) Fix agent: BLOCKED terminal command should terminate the agent loop early [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#24806Fetched 2026-05-14 03:51:39
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Author
Participants
Timeline (top)
labeled ×4cross-referenced ×2

When a user denies a terminal command (approval dialog), the tool returns:

{"output": "", "exit_code": -1, "error": "BLOCKED: Command denied by user. Do NOT retry this command.", "status": "blocked"}

The agent loop in run_agent.py (_execute_tool_calls_sequential) appends this as a normal tool message and continues running. The model is expected to stop retrying based on the text in the error string, but in practice it may continue attempting other commands or get stuck, keeping the agent alive until gateway drain timeout (60s) forces a kill.

Error Message

{"output": "", "exit_code": -1, "error": "BLOCKED: Command denied by user. Do NOT retry this command.", "status": "blocked"} The agent loop in run_agent.py (_execute_tool_calls_sequential) appends this as a normal tool message and continues running. The model is expected to stop retrying based on the text in the error string, but in practice it may continue attempting other commands or get stuck, keeping the agent alive until gateway drain timeout (60s) forces a kill.

Root Cause

When a user denies a terminal command (approval dialog), the tool returns:

{"output": "", "exit_code": -1, "error": "BLOCKED: Command denied by user. Do NOT retry this command.", "status": "blocked"}

The agent loop in run_agent.py (_execute_tool_calls_sequential) appends this as a normal tool message and continues running. The model is expected to stop retrying based on the text in the error string, but in practice it may continue attempting other commands or get stuck, keeping the agent alive until gateway drain timeout (60s) forces a kill.

Fix Action

Fixed

PR fix notes

PR #24812: fix(agent): exit loop early when terminal command is blocked by user

Description (problem / solution / changelog)

Closes #24806

Problem

When a user denies a terminal command via the approval dialog, terminal_tool returns a result with status: "blocked". The agent loop in _execute_tool_calls_sequential appended this as a normal tool message and continued running. The model was expected to stop based on the error text, but in practice the agent could keep running — and during gateway drain (e.g. triggered by a Feishu WS reconnect), this kept the agent alive for the full 60s timeout before being force-killed.

Fix

After each tool execution batch, scan the most recent tool messages for any terminal result with status == "blocked". If found, break the agent loop immediately with turn_exit_reason = "terminal_blocked", mirroring the existing guardrail_halt pattern.

Changes

  • run_agent.py: add blocked-terminal check after the guardrail halt check in the main agent loop

Changed files

  • run_agent.py (modified, +23/-0)

PR #24908: fix(run_agent): exit agent loop early when terminal command is denied

Description (problem / solution / changelog)

Summary

Fixes #24806: When a user denies a terminal command in the approval dialog (returns ), the agent was continuing to process remaining tool calls instead of exiting the loop.

Root Cause

in was not checking for on terminal results. It would record the blocked result and continue looping through remaining tool calls — creating a feedback loop where the model retries the same denied command repeatedly.

What was changed

**** — Three changes:

  1. Added attribute (line ~10415): A -typed attribute that stores the halt signal, analogous to .

  2. Added method (line ~10428): Creates and records a with , . Idempotent — no-op if already set.

  3. **Modified ** (line ~11385): After processing a tool result, checks for on a terminal tool call and calls . This causes 's existing guardrail-halt check to exit the loop immediately.

Why this approach

  • Reuses existing infrastructure for consistency
  • mirrors so handles both decision types uniformly via its existing halt check at line ~14780
  • No new exit paths — just wired into the existing guardrail halt mechanism
  • Idempotent: second calls are no-ops, so model retries don't double-set

Tests

— 9 tests covering:

  • sets the decision correctly
  • Second call is idempotent (same object reused)
  • Decision is cleared between turns (via )
  • exits early on blocked terminal
  • Non-blocked terminal result does NOT trigger early exit
  • Non-terminal blocked result does NOT trigger early exit
  • Subsequent tool calls are skipped after blocked terminal
  • Blocked result is added to messages before break
  • is called with the blocked message

9/9 pass, ruff clean.

Changed files

  • run_agent.py (modified, +39/-0)
  • tests/run_agent/test_terminal_blocked_exit.py (added, +209/-0)

Code Example

{"output": "", "exit_code": -1, "error": "BLOCKED: Command denied by user. Do NOT retry this command.", "status": "blocked"}
RAW_BUFFERClick to expand / collapse

Summary

When a user denies a terminal command (approval dialog), the tool returns:

{"output": "", "exit_code": -1, "error": "BLOCKED: Command denied by user. Do NOT retry this command.", "status": "blocked"}

The agent loop in run_agent.py (_execute_tool_calls_sequential) appends this as a normal tool message and continues running. The model is expected to stop retrying based on the text in the error string, but in practice it may continue attempting other commands or get stuck, keeping the agent alive until gateway drain timeout (60s) forces a kill.

Expected behavior

A status: "blocked" response from terminal_tool should trigger an early exit from the agent loop, similar to how _tool_guardrail_halt_decision works. The agent should finalize and return immediately after a user denial.

Actual behavior

The loop continues. If the gateway receives a SIGTERM (e.g. systemd restart due to WS reconnect), the agent is still running and gateway drain waits the full 60s before force-killing it.

Relevant code

  • tools/terminal_tool.py — produces status: "blocked"
  • run_agent.py _execute_tool_calls_sequential — no early exit on blocked status
  • agent/tool_guardrails.py — existing early-exit mechanism (not triggered by terminal BLOCKED)

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

A status: "blocked" response from terminal_tool should trigger an early exit from the agent loop, similar to how _tool_guardrail_halt_decision works. The agent should finalize and return immediately after a user denial.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING