Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion hindsight-embed/hindsight_embed/daemon_embed_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ def _detach_popen_kwargs(log_handle: IO[bytes]) -> dict:
"""Cross-platform kwargs to spawn a subprocess detached from the caller.

On POSIX, `start_new_session=True` calls setsid(2) so the child
survives the parent's terminal. On Windows there is no setsid: we use
survives the parent's terminal; `stdin` is pinned to /dev/null so the
child never inherits a caller fd 0 that may be CLOEXEC (closed at exec,
leaving ``sys.stdin = None``). On Windows there is no setsid: we use
`DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP`, which also means the
child has no console, so stdin/stdout/stderr MUST be redirected or any
write from the child crashes with "handle is invalid".
Expand All @@ -171,6 +173,7 @@ def _detach_popen_kwargs(log_handle: IO[bytes]) -> dict:
}
return {
"start_new_session": True,
"stdin": subprocess.DEVNULL,
"stdout": log_handle,
"stderr": log_handle,
}
Expand Down
16 changes: 15 additions & 1 deletion hindsight-embed/tests/test_embed_manager.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Tests for EmbedManager interface."""

import io
import subprocess
from unittest.mock import MagicMock, patch

from hindsight_embed import get_embed_manager
from hindsight_embed._http_probe import ProbeResponse
from hindsight_embed.daemon_embed_manager import DaemonEmbedManager
from hindsight_embed.daemon_embed_manager import DaemonEmbedManager, _detach_popen_kwargs


def _mock_sentence_transformers_present(monkeypatch):
Expand Down Expand Up @@ -684,3 +686,15 @@ def test_run_probe_still_reports_a_failed_command_as_none():
import sys as _sys

assert DaemonEmbedManager._run_probe([_sys.executable, "-c", "raise SystemExit(3)"]) is None


def test_detach_popen_kwargs_pins_stdin():
"""The daemon child must never inherit the caller's fd 0.

A caller can hold an fd 0 that is a socket opened with FD_CLOEXEC (e.g. a
TUI/gateway parent that wires its IPC channel onto fds 0-2). An inherited
fd 0 is closed by the kernel at exec, so the child would start with
``sys.stdin = None`` and crash in ``_redirect_stdio_to_log()``.
"""
kwargs = _detach_popen_kwargs(io.BytesIO())
assert kwargs["stdin"] == subprocess.DEVNULL
Loading