langchain-ai/deepagents · error · BrokenPipeError
Hook process pipes are unavailable
Error message
Hook process pipes are unavailable
What it means
`_communicate_bounded` writes the hook payload to the subprocess and reads bounded stdout/stderr over asyncio pipes. If any of the process's stdin/stdout/stderr streams is None, it raises `BrokenPipeError` — the subprocess was created without the piped streams the hook runner requires, so payload exchange is impossible.
Source
Thrown at libs/code/deepagents_code/hooks/runner.py:221
completion = asyncio.gather(task, return_exceptions=True)
try:
await asyncio.wait_for(
asyncio.shield(completion),
timeout=_TERMINATE_WAIT_TIMEOUT,
)
except TimeoutError:
task.cancel()
await completion
async def _communicate_bounded(
process: Process,
payload: bytes,
limit: int,
) -> tuple[bytes, bytes, bool, bool]:
if process.stdin is None or process.stdout is None or process.stderr is None:
msg = "Hook process pipes are unavailable"
raise BrokenPipeError(msg)
stdout_task = asyncio.create_task(_read_bounded(process.stdout, limit))
stderr_task = asyncio.create_task(_read_bounded(process.stderr, limit))
stdin_task = asyncio.create_task(_write_input(process, payload))
try:
await process.wait()
stdout, stderr = await asyncio.gather(stdout_task, stderr_task)
with contextlib.suppress(BrokenPipeError, ConnectionResetError):
await stdin_task
except BaseException:
stdin_task.cancel()
stdout_task.cancel()
stderr_task.cancel()
await asyncio.gather(
stdin_task,
stdout_task,
stderr_task,
return_exceptions=True,
)View on GitHub (pinned to a1af029e6e)
Solutions
- Create the hook process with `asyncio.create_subprocess_exec(..., stdin=PIPE, stdout=PIPE, stderr=PIPE)` so all three pipes exist
- Use the library's own process-creation path in `run_command_handler` rather than supplying an externally created Process
- If passing a Process across code paths, assert its stdin/stdout/stderr are non-None before communicating
Example fix
// before proc = await asyncio.create_subprocess_exec(*argv, stdout=DEVNULL) // after proc = await asyncio.create_subprocess_exec(*argv, stdin=PIPE, stdout=PIPE, stderr=PIPE)
Defensive patterns
Strategy: try-catch
Validate before calling
if proc.stdin is None or proc.stdout is None or proc.stderr is None:
raise BrokenPipeError("hook process must be created with stdin/stdout/stderr=PIPE") Type guard
def has_pipes(p: asyncio.subprocess.Process) -> bool:
return p.stdin is not None and p.stdout is not None and p.stderr is not None Try / catch
try:
stdout, stderr, _, _ = await _communicate_bounded(proc, payload, limit)
except BrokenPipeError as e:
logger.error("hook subprocess pipes unavailable: %s", e)
proc.kill() Prevention
- Always create hook subprocesses with stdin/stdout/stderr=PIPE
- Don't construct Process objects externally and pass them to the runner
- Communicate exactly once per process
When it happens
Trigger: `run_command_handler` spawned the hook `Process` without `stdin=PIPE`, `stdout=PIPE`, `stderr=PIPE` (or the streams were closed/None), then called `_communicate_bounded` on it.
Common situations: Custom/forked runner code creating the subprocess with inherited or devnull streams; process created by a different code path and passed in; platform/asyncio edge cases where stream objects are None before/after process exit.
Related errors
- Hook process stdin is unavailable
- Server process exited with code {process.returncode}
- Server process is not running
- Server process exited with code {self._process.returncode}
- Permission interrupted by hook
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/6e264b7c0b9d97d3.
Report an issue: GitHub.