langchain-ai/deepagents · error · BrokenPipeError

Hook process stdin is unavailable

Error message

Hook process stdin is unavailable

What it means

`_write_input` sends the serialized hook payload to the subprocess stdin. If `process.stdin` is None, it raises `BrokenPipeError` because there is no writable pipe to deliver the payload through. This is a defensive check inside the writer task spawned by `_communicate_bounded`.

Source

Thrown at libs/code/deepagents_code/hooks/runner.py:247

            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,
        )
        raise
    return stdout[0], stderr[0], stdout[1], stderr[1]


async def _write_input(process: Process, payload: bytes) -> None:
    if process.stdin is None:
        msg = "Hook process stdin is unavailable"
        raise BrokenPipeError(msg)
    process.stdin.write(payload)
    await process.stdin.drain()
    process.stdin.close()


async def _read_bounded(
    stream: asyncio.StreamReader,
    limit: int,
) -> tuple[bytes, bool]:
    retained = bytearray()
    truncated = False
    while chunk := await stream.read(_READ_CHUNK_BYTES):
        remaining = limit - len(retained)
        if remaining > 0:
            retained.extend(chunk[:remaining])
        if len(chunk) > remaining:
            truncated = True
    return bytes(retained), truncated

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Spawn the hook process with `stdin=asyncio.subprocess.PIPE` so stdin is writable
  2. Communicate with the process exactly once — do not close stdin or call another communicate before `_write_input` runs
  3. Guard custom code paths: check `process.stdin is not None` before attempting to write

Example fix

// before
proc = await asyncio.create_subprocess_exec(*argv)  # stdin inherited
// after
proc = await asyncio.create_subprocess_exec(*argv, stdin=asyncio.subprocess.PIPE, stdout=PIPE, stderr=PIPE)
Defensive patterns

Strategy: try-catch

Validate before calling

if proc.stdin is None:
    raise BrokenPipeError("hook subprocess was not created with stdin=PIPE")

Type guard

def writable_stdin(p: asyncio.subprocess.Process) -> bool:
    return p.stdin is not None and not p.stdin.is_closing()

Try / catch

try:
    await _communicate_bounded(proc, payload, limit)
except BrokenPipeError as e:
    logger.error("cannot write hook payload: %s", e)
    proc.kill()

Prevention

When it happens

Trigger: The hook subprocess was created without `stdin=PIPE` or its stdin was already closed/consumed, so `process.stdin` is None when `_communicate_bounded` schedules `_write_input` to send the payload.

Common situations: Subprocess created with inherited stdin or devnull; reusing a Process whose stdin was already closed by a prior communicate call; custom runner wiring that closes stdin before writing.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/a4e303b10b226f64. Report an issue: GitHub.