Skip to content

ShellToolMiddleware may kill caller process group when create_process_group=False #36358

@yashodipmore

Description

Checked other resources

  • This is a bug, not a usage question.
  • I added a clear and descriptive title that summarizes this issue.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.
  • The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.

Package (Required)

  • langchain
  • langchain-openai
  • langchain-anthropic
  • langchain-classic
  • langchain-core
  • langchain-model-profiles
  • langchain-tests
  • langchain-text-splitters
  • langchain-chroma
  • langchain-deepseek
  • langchain-exa
  • langchain-fireworks
  • langchain-groq
  • langchain-huggingface
  • langchain-mistralai
  • langchain-nomic
  • langchain-ollama
  • langchain-openrouter
  • langchain-perplexity
  • langchain-qdrant
  • langchain-xai
  • Other / not sure / general

Related Issues / PRs

No response

Reproduction Steps / Example Code (Python)

import os
import tempfile
from pathlib import Path

from langchain.agents.middleware._execution import HostExecutionPolicy
from langchain.agents.middleware.shell_tool import ShellSession


def main() -> None:
    policy = HostExecutionPolicy(
        create_process_group=False,
        command_timeout=0.1,
        termination_timeout=0.05,
    )

    session = ShellSession(
        workspace=Path(tempfile.mkdtemp(prefix="lc-shell-repro-")),
        policy=policy,
        command=("/bin/bash",),
        environment={},
    )

    kill_calls: list[tuple[int, int]] = []
    original_killpg = os.killpg
    try:
        # Safety: capture killpg call instead of actually killing the process group.
        os.killpg = lambda pgid, sig: kill_calls.append((pgid, sig))  # type: ignore[assignment]

        session.start()
        _ = session.execute("sleep 5", timeout=policy.command_timeout)

        current_pgid = os.getpgrp()
        child_pgid = os.getpgid(session._process.pid) if session._process else None

        print("create_process_group:", policy.create_process_group)
        print("current_pgid:", current_pgid)
        print("child_pgid:", child_pgid)
        print("kill_calls:", kill_calls)

        if not kill_calls:
            raise RuntimeError("Expected _kill_process to call os.killpg at least once.")
        if kill_calls[0][0] != current_pgid:
            raise RuntimeError(
                "Expected killpg target process group to match caller process group."
            )
    finally:
        os.killpg = original_killpg  # type: ignore[assignment]
        session.stop(0.2)


if __name__ == "__main__":
    main()

Error Message and Stack Trace (if applicable)

No Python exception is required to observe the bug. The bug is behavioral and visible from the printed output.

Example output:
create_process_group: False
current_pgid: 249430
child_pgid: 249430
kill_calls: [(249430, 9)]

Description

  • I am using ShellToolMiddleware internals with HostExecutionPolicy(create_process_group=False).
  • I expect timeout cleanup to terminate only the child shell process.
  • Instead, _kill_process uses process-group kill unconditionally via os.killpg(os.getpgid(child_pid), SIGKILL).
  • When create_process_group=False, the child shares the caller process group, so the kill target can include the caller and sibling processes.

System Info

System Info
System Information

OS: Linux
OS Version: #19~24.04.2-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 6 23:08:46 UTC 2
Python Version: 3.12.3 (main, Mar 3 2026, 12:15:18) [GCC 13.3.0]

Package Information

Optional packages not installed

langsmith
deepagents
deepagents-cli

Metadata

Metadata

Labels

bugRelated to a bug, vulnerability, unexpected error with an existing featureexternallangchain`langchain` package issues & PRs

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions