fix(tools): prevent worker deadlock when agent backgrounds server via shell & (#68915) - #70549
webtecnica wants to merge 1 commit into
Conversation
…ker deadlock on server backgrounding Issue NousResearch#68915: when the agent runs a compound command with trailing & (e.g. `cd /app && node server.js &`), bash parses it as `(A && B) &` — a subshell that holds the stdout pipe open forever when B is a long-running server. The existing _rewrite_compound_background in terminal_tool.py correctly rewrites this to `A && { B & }` to avoid the subshell fork, but it was only applied in the foreground execute() path (tools/environments/base.py). The background spawn_local() path bypasses base.py entirely and passed the raw command directly to Popen/PTY, leaving the deadlock unmitigated. Fix: apply _rewrite_compound_background in spawn_local() before the command is passed to Popen or PTY spawn. Uses a lazy import to avoid circular dependency (terminal_tool imports process_registry). - PTY spawn path: now uses safe_command (rewritten) - Popen spawn path: now uses safe_command (rewritten) - Session.command still stores the original (unrewritten) command for display - Simple `cmd &` is left unchanged (no subshell bug) Tests: 4 regression tests verifying (1) compound is rewritten, (2) simple bg is preserved, (3) multi-line compounds are rewritten, (4) session.command stores original.
|
Closing as duplicate of #70884 (salvage of #70546). Both your spawn_local fix and the telegram connect fix were salvaged together. Your authorship preserved via cherry-pick. Note: the commits in #70546 were authored under "Hermes Agent" identity — added contributor email mapping (agent@hermes.dev -> webtecnica) in the salvage PR. |
Follow-up to salvaged PR NousResearch#70549: - Replace fragile 'or' assertions with single precise checks that catch partial-rewrite regressions (would have masked a missing closing brace) - Add test_pty_path_uses_rewritten_command covering the PTY spawn path that was modified but previously untested
Follow-up to salvaged PR #70549: - Replace fragile 'or' assertions with single precise checks that catch partial-rewrite regressions (would have masked a missing closing brace) - Add test_pty_path_uses_rewritten_command covering the PTY spawn path that was modified but previously untested
|
Merged via #70876 — your fix was cherry-picked with authorship preserved, and the original fix landed on main (commit d7512c8). Your PR identified that Thanks for the contribution! |
Follow-up to salvaged PR NousResearch#70549: - Replace fragile 'or' assertions with single precise checks that catch partial-rewrite regressions (would have masked a missing closing brace) - Add test_pty_path_uses_rewritten_command covering the PTY spawn path that was modified but previously untested
Follow-up to salvaged PR NousResearch#70549: - Replace fragile 'or' assertions with single precise checks that catch partial-rewrite regressions (would have masked a missing closing brace) - Add test_pty_path_uses_rewritten_command covering the PTY spawn path that was modified but previously untested
Follow-up to salvaged PR NousResearch#70549: - Replace fragile 'or' assertions with single precise checks that catch partial-rewrite regressions (would have masked a missing closing brace) - Add test_pty_path_uses_rewritten_command covering the PTY spawn path that was modified but previously untested
Problem
When LLM issues
A && B &via terminal, bash creates a subshell that holds stdout pipe open → worker deadlocks permanently (requires kill -9).Root Cause
The existing
_rewrite_compound_backgroundfix only ran in the foreground path (base.py:execute()). The backgroundspawn_local()path inprocess_registry.pybypassed it entirely.Fix
_rewrite_compound_backgroundcall inspawn_local()before Popen/PTY spawncmd &(without &&) preserved unchangedFixes #68915