langchain - ✅(Solved) Fix ShellSession times out when stdout has no trailing newline and the done marker is appended to the same line [2 pull requests, 2 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
langchain-ai/langchain#36696Fetched 2026-04-15 06:19:58
View on GitHub
Comments
2
Participants
3
Timeline
8
Reactions
0
Author
Timeline (top)
commented ×2cross-referenced ×2labeled ×2referenced ×2

ShellSession.execute() currently injects a completion marker into stdout using a separate printf call, and _collect_output() only recognizes command completion when a stdout line starts with that marker.

However, stdout is read with readline(). If the command output does not end with a trailing newline, the command's final stdout bytes and the injected done marker are returned as a single line, for example:

hello without newline__LC_SHELL_DONE__<id> 0

In that case, the current logic:

if source == "stdout" and data.startswith(marker):

misses the marker because it no longer appears at the start of the line. The command is then treated as still running until command_timeout is reached.

This affects minimal cases such as printf 'hello without newline' and can also affect file reads where the file does not end with a newline.

A minimal fix is to detect the marker anywhere in the stdout line, preserve any real stdout content that appears before it, and still parse the exit code from the marker suffix.

Error Message

There is no Python exception. The bug is behavioral.

Expected: CommandExecutionResult(output='hello without newline', exit_code=0, timed_out=False, ...)

Actual on current master before the fix: CommandExecutionResult(output='', exit_code=None, timed_out=True, ...)

Root Cause

misses the marker because it no longer appears at the start of the line. The command is then treated as still running until command_timeout is reached.

Fix Action

Fix / Workaround

  • 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.

Other Dependencies

httpx: 0.28.1 jsonpatch: 1.33 langgraph: 1.1.5 numpy: 2.3.4 openai: 2.26.0 orjson: 3.11.6 packaging: 24.2 pydantic: 2.12.5 pytest: 9.0.2 pytest-asyncio: 1.3.0 pytest-benchmark: 5.2.3 pytest-codspeed: 4.2.0 pytest-recording: 0.13.4 pytest-socket: 0.7.0 pyyaml: 6.0.3 requests: 2.33.0 requests-toolbelt: 1.0.0 rich: 14.2.0 syrupy: 5.1.0 tenacity: 9.1.2 tiktoken: 0.12.0 typing-extensions: 4.15.0 uuid-utils: 0.12.0 vcrpy: 8.1.1 zstandard: 0.25.0

PR fix notes

PR #36695: fix(langchain): handle done markers after stdout without trailing newline

Description (problem / solution / changelog)

Fixes #36696

Summary

  • handle shell done markers when stdout does not end with a trailing newline
  • preserve stdout content that appears before the marker on the same line
  • add regression coverage for both normal and truncated non-newline stdout

Why

ShellSession currently only recognizes completion when a stdout line starts with the done marker. If a command writes stdout without a trailing newline, the injected marker is appended to that same line instead. In that case, the marker is missed and the command can time out even though it already finished.

Testing

  • uv run ruff check langchain/agents/middleware/shell_tool.py tests/unit_tests/agents/middleware/implementations/test_shell_tool.py
  • uv run pytest tests/unit_tests/agents/middleware/implementations/test_shell_tool.py -k 'stdout_without_trailing_newline or truncated_stdout_without_trailing_newline or stderr_output_labeling'

AI disclaimer

This PR was drafted with the help of an AI coding agent, then reviewed and refined by a human.

Changed files

  • libs/langchain_v1/langchain/agents/middleware/shell_tool.py (modified, +34/-25)
  • libs/langchain_v1/tests/unit_tests/agents/middleware/implementations/test_shell_tool.py (modified, +55/-0)

PR #36740: fix(langchain): detect done marker anywhere in stdout line to fix no-trailing-newline timeout

Description (problem / solution / changelog)

Fixes #36696

ShellSession._collect_output() used data.startswith(marker) to detect command completion. When a command's stdout has no trailing newline, readline() returns the command output and the injected done-marker on the same line (e.g. hello without newline__LC_SHELL_DONE__<id> 0). startswith() misses the marker, and the command times out waiting for it.

Fix: use marker in data instead of data.startswith(marker). When the marker is found mid-line, str.partition(marker) extracts any real stdout that precedes it before parsing the exit code from the suffix.

Change: one conditional in _collect_output() in libs/langchain_v1/langchain/agents/middleware/shell_tool.py.

Tests added in libs/langchain_v1/tests/unit_tests/agents/middleware/implementations/test_shell_tool.py:

  • test_command_output_without_trailing_newlineprintf 'text' returns output without timing out
  • test_command_output_without_trailing_newline_exit_code — exit code is correctly parsed when marker is mid-line

How I verified it works

Ran the logic against all four cases manually:

  • Normal line with trailing newline + marker at start ✅
  • Output + marker on same line (the bug case) ✅
  • Non-zero exit code with mid-line marker ✅
  • Confirmed old startswith() returns False for the bug case ✅

Changed files

  • libs/langchain_v1/langchain/agents/middleware/shell_tool.py (modified, +9/-2)
  • libs/langchain_v1/tests/unit_tests/agents/middleware/implementations/test_shell_tool.py (modified, +60/-0)

Code Example

import tempfile
from pathlib import Path

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

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

session.start()
try:
    result = session.execute("printf 'hello without newline'", timeout=policy.command_timeout)
    print(result)
finally:
    session.stop(1.0)

---

There is no Python exception. The bug is behavioral.

Expected:
CommandExecutionResult(output='hello without newline', exit_code=0, timed_out=False, ...)

Actual on current `master` before the fix:
CommandExecutionResult(output='', exit_code=None, timed_out=True, ...)

---

hello without newline__LC_SHELL_DONE__<id> 0

---

if source == "stdout" and data.startswith(marker):

---

System Information
------------------
> OS:  Linux
> OS Version:  #1 SMP Fri Mar  6 10:10:19 UTC 2026
> Python Version:  3.13.12 (main, Feb 12 2026, 00:43:56) [Clang 21.1.4 ]

Package Information
-------------------
> langchain_core: 1.3.0a1
> langchain: 1.2.15
> langsmith: 0.6.3
> langchain_openai: 1.1.12
> langchain_tests: 1.1.6
> langgraph_sdk: 0.3.3

Optional packages not installed
-------------------------------
> deepagents
> deepagents-cli

Other Dependencies
------------------
> httpx: 0.28.1
> jsonpatch: 1.33
> langgraph: 1.1.5
> numpy: 2.3.4
> openai: 2.26.0
> orjson: 3.11.6
> packaging: 24.2
> pydantic: 2.12.5
> pytest: 9.0.2
> pytest-asyncio: 1.3.0
> pytest-benchmark: 5.2.3
> pytest-codspeed: 4.2.0
> pytest-recording: 0.13.4
> pytest-socket: 0.7.0
> pyyaml: 6.0.3
> requests: 2.33.0
> requests-toolbelt: 1.0.0
> rich: 14.2.0
> syrupy: 5.1.0
> tenacity: 9.1.2
> tiktoken: 0.12.0
> typing-extensions: 4.15.0
> uuid-utils: 0.12.0
> vcrpy: 8.1.1
> zstandard: 0.25.0
RAW_BUFFERClick to expand / collapse

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

  • Related PR: #36695
  • Similar but different fixes in the same area: #34333, #34535

Reproduction Steps / Example Code (Python)

import tempfile
from pathlib import Path

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

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

session.start()
try:
    result = session.execute("printf 'hello without newline'", timeout=policy.command_timeout)
    print(result)
finally:
    session.stop(1.0)

Error Message and Stack Trace (if applicable)

There is no Python exception. The bug is behavioral.

Expected:
CommandExecutionResult(output='hello without newline', exit_code=0, timed_out=False, ...)

Actual on current `master` before the fix:
CommandExecutionResult(output='', exit_code=None, timed_out=True, ...)

Description

ShellSession.execute() currently injects a completion marker into stdout using a separate printf call, and _collect_output() only recognizes command completion when a stdout line starts with that marker.

However, stdout is read with readline(). If the command output does not end with a trailing newline, the command's final stdout bytes and the injected done marker are returned as a single line, for example:

hello without newline__LC_SHELL_DONE__<id> 0

In that case, the current logic:

if source == "stdout" and data.startswith(marker):

misses the marker because it no longer appears at the start of the line. The command is then treated as still running until command_timeout is reached.

This affects minimal cases such as printf 'hello without newline' and can also affect file reads where the file does not end with a newline.

A minimal fix is to detect the marker anywhere in the stdout line, preserve any real stdout content that appears before it, and still parse the exit code from the marker suffix.

System Info

System Information
------------------
> OS:  Linux
> OS Version:  #1 SMP Fri Mar  6 10:10:19 UTC 2026
> Python Version:  3.13.12 (main, Feb 12 2026, 00:43:56) [Clang 21.1.4 ]

Package Information
-------------------
> langchain_core: 1.3.0a1
> langchain: 1.2.15
> langsmith: 0.6.3
> langchain_openai: 1.1.12
> langchain_tests: 1.1.6
> langgraph_sdk: 0.3.3

Optional packages not installed
-------------------------------
> deepagents
> deepagents-cli

Other Dependencies
------------------
> httpx: 0.28.1
> jsonpatch: 1.33
> langgraph: 1.1.5
> numpy: 2.3.4
> openai: 2.26.0
> orjson: 3.11.6
> packaging: 24.2
> pydantic: 2.12.5
> pytest: 9.0.2
> pytest-asyncio: 1.3.0
> pytest-benchmark: 5.2.3
> pytest-codspeed: 4.2.0
> pytest-recording: 0.13.4
> pytest-socket: 0.7.0
> pyyaml: 6.0.3
> requests: 2.33.0
> requests-toolbelt: 1.0.0
> rich: 14.2.0
> syrupy: 5.1.0
> tenacity: 9.1.2
> tiktoken: 0.12.0
> typing-extensions: 4.15.0
> uuid-utils: 0.12.0
> vcrpy: 8.1.1
> zstandard: 0.25.0

extent analysis

TL;DR

The issue can be fixed by modifying the ShellSession.execute() method to detect the completion marker anywhere in the stdout line, not just at the start.

Guidance

  • The current implementation of ShellSession.execute() uses readline() to read stdout, which can cause issues when the command output does not end with a newline.
  • To fix this, the completion marker detection logic should be modified to check for the marker anywhere in the stdout line, not just at the start.
  • The fix should preserve any real stdout content that appears before the marker and still parse the exit code from the marker suffix.
  • The related PR #36695 and similar fixes in #34333 and #34535 may provide additional context and guidance for implementing the fix.

Example

# Modified completion marker detection logic
if source == "stdout" and marker in data:
    # Preserve real stdout content before the marker
    output = data.split(marker)[0]
    # Parse exit code from the marker suffix
    exit_code = int(data.split(marker)[1].split()[0])
    # ...

Notes

  • The fix should be applied to the ShellSession.execute() method in the langchain package.
  • The modified completion marker detection logic should be thoroughly tested to ensure it works correctly for different command outputs and edge cases.

Recommendation

Apply workaround: Modify the ShellSession.execute() method to detect the completion marker anywhere in the stdout line, as described in the guidance section. This fix should resolve the issue and provide the expected output for commands that do not end with a newline.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING