hermes - ✅(Solved) Fix bug(terminal): safety filter false-positives on background wrapper keywords inside quoted strings [5 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#20064Fetched 2026-05-06 06:38:56
View on GitHub
Comments
0
Participants
1
Timeline
8
Reactions
0
Participants
Timeline (top)
cross-referenced ×5labeled ×3

Fix Action

Fix / Workaround

This is a significant usability issue — any session dealing with process management code (reviewing PRs, writing commit messages, debugging subprocess issues) hits this repeatedly, requiring workarounds like writing content to files instead of passing inline.

PR fix notes

PR #20066: fix(terminal): prevent safety filter false positives on keywords inside quoted strings

Description (problem / solution / changelog)

Problem

The terminal tool's safety filter blocks legitimate commands when keywords like nohup, disown, or setsid appear inside quoted strings — e.g., in commit messages, Python -c code, gh pr create --body text, or grep patterns.

Examples that get incorrectly blocked:

python3 -c "x = 'preexec_fn=os.setsid'"
git commit -m "fix: replace preexec_fn=os.setsid with process_group=0"
gh pr create --body "We removed os.setsid..."
grep -r 'nohup' tests/

Root Cause

_foreground_background_guidance() in tools/terminal_tool.py uses:

_SHELL_LEVEL_BACKGROUND_RE = re.compile(r"\b(?:nohup|disown|setsid)\b", re.IGNORECASE)

This matches the keyword anywhere in the full command text, including inside quoted strings.

Fix

Two-layer approach:

  1. _strip_quotes() helper — Removes single-quoted, double-quoted, and backtick-quoted content before pattern matching, so keywords inside strings are invisible to the regexes.

  2. Position-aware regex — Tightened to only match keywords at shell command positions (after ^, ;, &&, ||, $() rather than at any word boundary.

Both layers are needed: quote stripping handles the common case (keywords in string literals), and the position-aware regex handles unquoted edge cases.

Testing

  • All 14 test_terminal_tool.py tests pass
  • All 45 test_terminal_compound_background.py + test_terminal_foreground_timeout_cap.py tests pass
  • Verified 7 false-positive cases now pass (quoted keywords)
  • Verified 5 true-positive cases still blocked (actual background wrapper usage)

Fixes #20064

Changed files

  • tools/terminal_tool.py (modified, +28/-4)

PR #20079: fix(terminal): prevent safety filter false positives on keywords inside quoted strings

Description (problem / solution / changelog)

Summary

The background-wrapper detection regex (nohup/disown/setsid) matched keywords anywhere in the command text, including inside quoted strings. This blocked legitimate commands:

git commit -m "fix: replace preexec_fn=os.setsid with process_group=0"
grep -r 'nohup' tests/
python3 -c "x = 'preexec_fn=os.setsid'"
gh pr create --body "We removed os.setsid..."

Fix

Added _strip_quoted_content() which removes content inside single/double/backtick quotes before the regex match. Only keywords in the actual shell structure trigger the safety filter.

Testing

  • All 14 test_terminal_tool.py tests pass
  • All 45 compound_background + foreground_timeout_cap tests pass
  • 4 false-positive cases now correctly pass through
  • 4 true-positive cases still correctly blocked

Closes #20064 Closes #20066

Changed files

  • tools/terminal_tool.py (modified, +40/-1)

PR #20099: fix: reduce false positives in terminal safety filter for nohup/disown/setsid

Description (problem / solution / changelog)

What

Fix the terminal tool's safety filter regex to only match nohup/disown/setsid when they are used as actual shell commands, not when they appear inside quoted strings, commit messages, or code.

Fixes #20064

The Bug

The _SHELL_LEVEL_BACKGROUND_RE regex uses a naive \b(?:nohup|disown|setsid)\b pattern that matches these keywords anywhere in the command string. This causes false positives that block legitimate commands:

# All of these get incorrectly blocked:
git commit -m "fix: replace setsid with process_group"
python3 -c "x = 'preexec_fn=os.setsid'"
echo "The function os.setsid() creates a new session"
gh pr create --body "We removed setsid from the code"
grep -r setsid .

Users hit this frequently when:

  • Writing commit messages about process management
  • Reviewing PRs that mention these functions
  • Running Python/Ruby code that references os.setsid()
  • Using echo/grep to search for these terms

Fix

Replace the naive regex with one that only matches when the keyword appears at the start of a command or after shell operators (;, |, &&, ||, subshell ():

# Before (matches anywhere in string):
_SHELL_LEVEL_BACKGROUND_RE = re.compile(r"\b(?:nohup|disown|setsid)\b", re.IGNORECASE)

# After (matches only as actual commands):
_SHELL_LEVEL_BACKGROUND_RE = re.compile(
    r"(?:^|[;&|]\s*|&&\s*|\|\|\s*|\(\s*)(?:nohup|disown|setsid)\b",
    re.IGNORECASE | re.MULTILINE,
)

Before vs After

CommandBeforeAfter
setsid my_serverBlockedBlocked (actual usage)
nohup ./script.sh &BlockedBlocked (actual usage)
cmd; setsid serverBlockedBlocked (after ;)
git commit -m "replace setsid"BlockedAllowed (in string)
python3 -c "os.setsid()"BlockedAllowed (in string)
echo 'os.setsid() creates'BlockedAllowed (in string)
grep -r setsid .BlockedAllowed (argument)

Tests

Added 5 new tests to tests/tools/test_terminal_foreground_timeout_cap.py:

  • test_foreground_allows_setsid_mentioned_in_string — commit message
  • test_foreground_allows_setsid_in_python_code — Python code string
  • test_foreground_allows_setsid_in_echo — echo text
  • test_foreground_rejects_setsid_after_semicolon — actual ; chaining
  • test_foreground_rejects_setsid_after_pipe — actual pipe usage

All 16 tests pass (11 existing + 5 new).

Changed files

  • tests/tools/test_terminal_foreground_timeout_cap.py (modified, +88/-0)
  • tools/terminal_tool.py (modified, +4/-1)

PR #20101: fix(terminal): strip quoted strings before safety-filter regex scan (#20064)

Description (problem / solution / changelog)

Summary

Fixes #20064

The _foreground_background_guidance function used a naive word-boundary regex to detect shell-level background wrappers (nohup/disown/setsid). This caused false positives whenever these keywords appeared inside quoted arguments — commit messages, Python -c strings, PR body text, grep patterns, etc.:

# All of these were incorrectly blocked:
git commit -m "fix: replace preexec_fn=os.setsid with process_group=0"
python3 -c "x = 'preexec_fn=os.setsid'"
gh pr create --body "We removed the nohup call"
echo 'The nohup command prevents SIGHUP from killing the process'
grep -r 'disown' ./docs/

Root Cause

_SHELL_LEVEL_BACKGROUND_RE scanned the entire command string including quoted content, so any occurrence of the keywords triggered the filter regardless of context.

Fix

Add a _strip_quoted_strings() helper that removes single- and double-quoted string literals (honouring backslash escapes) before the safety-filter regexes are applied. All three pattern checks (_SHELL_LEVEL_BACKGROUND_RE, _INLINE_BACKGROUND_AMP_RE, _TRAILING_BACKGROUND_AMP_RE, and _LONG_LIVED_FOREGROUND_PATTERNS) now operate on the unquoted shell structure.

# NEW: strip quoted content first
unquoted = _strip_quoted_strings(command)
if _SHELL_LEVEL_BACKGROUND_RE.search(unquoted):   # ← was: search(command)
    ...

Verified

  • All 6 false-positive cases above now pass through correctly (no error)
  • All true-positive cases (bare nohup/setsid/disown, & backgrounding, long-lived server patterns) still fire correctly
  • All 11 existing test_terminal_foreground_timeout_cap.py tests continue to pass
  • 20 new regression tests added in tests/tools/test_terminal_safety_filter_false_positives.py

Changed files

  • agent/auxiliary_client.py (modified, +46/-10)
  • run_agent.py (modified, +8/-5)
  • tests/run_agent/test_compression_feasibility.py (modified, +65/-0)
  • tests/tools/test_terminal_safety_filter_false_positives.py (added, +151/-0)
  • tools/terminal_tool.py (modified, +26/-3)

PR #20146: fix(terminal): stop false-positive on quoted nohup/disown/setsid (#20064)

Description (problem / solution / changelog)

Closes #20064.

The terminal tool's _foreground_background_guidance() regex was matching nohup/disown/setsid anywhere in the command string, including inside quoted strings, Python -c code, commit messages, and PR bodies. Legit commands like git commit -m "replace preexec_fn=os.setsid with process_group=0" got blocked with a request to use terminal(background=true).

Fix

Two-layer match:

  1. Strip single/double-quoted spans before matching, so quoted occurrences don't fire the filter.
  2. Boundary-anchored regex — only matches at start of command, after ;, |, &&, ||, newline, or open-paren. So even unquoted text like xargs setsid foo doesn't accidentally re-fire.

The issue suggested either approach; this PR uses both because each catches cases the other misses.

Verified

  • All 4 false-positive cases from the issue body now pass the filter.
  • Genuine setsid foo, nohup ./script.sh &, cmd && nohup foo, (setsid foo), multi-line, and mixed-case still get redirected to background=true.
  • 35 new pinned cases in tests/tools/test_terminal_quoted_background_wrapper_filter.py.
  • Pre-existing terminal tests (114 tests across test_terminal_tool.py, test_terminal_compound_background.py, test_terminal_requirements.py, test_terminal_foreground_timeout_cap.py) still pass.

Notes

  • Quote-stripping doesn't special-case ANSI-C $'...' or heredocs; the boundary regex handles those by requiring a command-position prefix, keeping false-positive risk low. Documented in the helper docstring.
  • No CHANGELOG.md exists in this repo — release notes are auto-generated from conventional commits, so the fix(terminal): prefix is enough.

Changed files

  • tests/tools/test_terminal_quoted_background_wrapper_filter.py (added, +174/-0)
  • tools/terminal_tool.py (modified, +56/-2)

Code Example

_SHELL_LEVEL_BACKGROUND_RE = re.compile(r"\b(?:nohup|disown|setsid)\b", re.IGNORECASE)

---

# Python code mentioning the word in a string
python3 -c "x = 'preexec_fn=os.setsid'"

# Git commit messages
git commit -m "fix: replace preexec_fn=os.setsid with process_group=0"

# PR creation with body text
gh pr create --body "We removed preexec_fn=os.setsid..."

# Even echo or grep
echo "The function os.setsid() creates a new session"

---

setsid my_server          # actual setsid usage → should block
nohup ./script.sh &       # actual nohup usage → should block

---

Foreground command uses shell-level background wrappers (nohup/disown/setsid).
Use terminal(background=true) so Hermes can track the process...

---

# Match only when the keyword is used as a command (not inside quotes or arguments)
_SHELL_LEVEL_BACKGROUND_RE = re.compile(
    r"(?:^|[;&|]\s*|&&\s*|\|\|\s*|\(\s*)(?:nohup|disown|setsid)\b",
    re.IGNORECASE | re.MULTILINE,
)
RAW_BUFFERClick to expand / collapse

Bug Description

The terminal tool's _foreground_background_guidance() function uses a naive regex to detect shell-level background wrappers:

_SHELL_LEVEL_BACKGROUND_RE = re.compile(r"\b(?:nohup|disown|setsid)\b", re.IGNORECASE)

This regex fires on any occurrence of the word in the full command string, including inside quoted strings, Python code, commit messages, git operations, and PR body text. This causes false positives that block legitimate commands.

Steps to Reproduce

Any of these get blocked:

# Python code mentioning the word in a string
python3 -c "x = 'preexec_fn=os.setsid'"

# Git commit messages
git commit -m "fix: replace preexec_fn=os.setsid with process_group=0"

# PR creation with body text
gh pr create --body "We removed preexec_fn=os.setsid..."

# Even echo or grep
echo "The function os.setsid() creates a new session"

Expected Behavior

The filter should only block commands where nohup/disown/setsid are used as actual shell-level process management commands, e.g.:

setsid my_server          # actual setsid usage → should block
nohup ./script.sh &       # actual nohup usage → should block

It should NOT block when these words appear in:

  • Quoted strings (single or double)
  • Here-documents
  • Command arguments (commit messages, PR bodies, echo text)
  • Python/Ruby/etc. code passed via -c

Actual Behavior

All commands containing the word anywhere in the text are blocked with:

Foreground command uses shell-level background wrappers (nohup/disown/setsid).
Use terminal(background=true) so Hermes can track the process...

Proposed Fix

The regex should only match when the keyword appears as the first word of the command or immediately after ;, |, &&, ||, or at the start of a subshell. A possible approach:

# Match only when the keyword is used as a command (not inside quotes or arguments)
_SHELL_LEVEL_BACKGROUND_RE = re.compile(
    r"(?:^|[;&|]\s*|&&\s*|\|\|\s*|\(\s*)(?:nohup|disown|setsid)\b",
    re.IGNORECASE | re.MULTILINE,
)

This is conservative but covers the main false-positive cases. A more robust fix would be to strip quoted content before matching.

Environment

  • macOS 26.3.1 (Tahoe)
  • hermes-agent main (commit 7c0fa26fd)
  • Affects: tools/terminal_tool.py line 1542

Impact

This is a significant usability issue — any session dealing with process management code (reviewing PRs, writing commit messages, debugging subprocess issues) hits this repeatedly, requiring workarounds like writing content to files instead of passing inline.

extent analysis

TL;DR

Update the _SHELL_LEVEL_BACKGROUND_RE regex to only match keywords as the first word of a command or after specific shell operators.

Guidance

  • Verify the issue by testing the provided examples and checking if the filter incorrectly blocks commands with the keywords in quoted strings or as arguments.
  • Implement the proposed fix by updating the _SHELL_LEVEL_BACKGROUND_RE regex to the suggested pattern, which matches keywords after shell operators or at the start of a subshell.
  • Test the updated regex with various commands to ensure it correctly blocks only intended usage of nohup, disown, and setsid.
  • Consider a more robust fix that strips quoted content before matching to further reduce false positives.

Example

_SHELL_LEVEL_BACKGROUND_RE = re.compile(
    r"(?:^|[;&|]\s*|&&\s*|\|\|\s*|\(\s*)(?:nohup|disown|setsid)\b",
    re.IGNORECASE | re.MULTILINE,
)

Notes

The proposed fix may still have edge cases, and a more comprehensive solution might involve parsing the command string more thoroughly.

Recommendation

Apply the proposed workaround by updating the _SHELL_LEVEL_BACKGROUND_RE regex, as it provides a significant reduction in false positives and improves usability.

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…

Still need to ship something?

×6

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

Back to top recommendations

TRENDING