Follow-up fixes for salvaged PR #67686 (issue #67639):
1. Windows regression: bare 'import fcntl' at module level in entry.py and
slash_worker.py would crash on Windows (fcntl is POSIX-only). entry.py
explicitly aims to 'import cleanly on Windows'. Extracted all fcntl/socket
logic to tui_gateway/_stdin_recovery.py with try/except import guards.
2. fd leak: socket.fromfd() dups fd 0; s.detach() returned the fd number
without closing it, leaking one fd per recovery call. Changed to s.close()
(safe — fromfd duped the fd, closing won't close stdin).
3. Code duplication: ~80 lines of recovery loop + diagnostic were copy-pasted
between entry.py and slash_worker.py. Extracted to shared
tui_gateway/_stdin_recovery.py (handle_spurious_eof + diagnose_stdin_state).
4. Profile-unsafe path: scanner used Path.home() / '.hermes' / 'plugins' but
canonical plugin discovery uses get_hermes_home() / 'plugins'. With
HERMES_HOME pointing elsewhere, scanner missed the actual plugin dir.
Also gated project plugins behind HERMES_ENABLE_PROJECT_PLUGINS to match
hermes_cli/plugins.py.
5. SO_RCVTIMEO not cleared: recovery only called os.set_blocking(0, True)
but a child that set SO_RCVTIMEO would cause the next readline to time
out and loop. Now clears SO_RCVTIMEO alongside O_NONBLOCK.
The blanket DEVNULL pass muzzled run_oauth_setup_token()'s interactive
'claude setup-token' login, which needs inherited stdin to prompt the
user. Revert that one call and replace the guard's brittle file:line
whitelist with an inline 'noqa: subprocess-stdin' marker that travels
with the code.
scripts/check_subprocess_stdin.py scans agent/, tools/, plugins/, and
tui_gateway/ for subprocess.run() and subprocess.Popen() calls that
don't explicitly set stdin=. Missing stdin= means the child inherits the
parent's fd, which in TUI mode is the JSON-RPC pipe — causing gateway
crashes on stdin EOF.
Exits 0 (pass) or 1 (violations found). Can be run manually or added to
CI. Skips comments, docstring references, and calls that use input= (which
creates its own pipe).
Usage: python scripts/check_subprocess_stdin.py