ShellTool: empty ShellResult(output=[]) fabricates stdout "(no output)" in the model-visible shell_call_output and persisted shell_output
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 77/100
Hướng nghiên cứu
Bắt đầu tại src/agents/run_internal/tool_actions.py, ở ShellAction.execute, nơi payload đã tuần tự hóa rỗng chuyển sang nhánh dự phòng dùng văn bản đã render. Đọc các nhánh liền kề của ShellResult và chuỗi thông thường, sau đó chạy tests/test_shell_tool.py cùng trường hợp tái hiện được cung cấp bằng ScriptedModel. Hoàn thành khi kết quả có cấu trúc rỗng tạo ra output: [] mà không có mục shell_output nào được lưu lại, đồng thời các trường hợp liền kề vẫn vượt qua.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Describe the bug
When a ShellTool executor returns a structured ShellResult with an empty output list — i.e. ShellResult(output=[]), the legitimate result of a command that ran successfully and printed nothing (e.g. true, mkdir foo) — the SDK fabricates stdout that the executor never produced:
- the model-visible
shell_call_outputpayload for the next turn contains[{"stdout": "(no output)", "stderr": "", "outcome": {"type": "exit", "exit_code": 0}}] - the same fabricated entry is persisted into
raw_item["shell_output"]with"outcome": "success"
"(no output)" is the human-readable placeholder that render_shell_outputs() returns for an empty output list, not command output. The model can quote it back to the user as real stdout, and any consumer parsing the structured shell_call_output entries (or the persisted shell_output) sees fake command output for a command that produced none.
Debug information
- Agents SDK version:
0.23.1(latest release; also reproduced onmain@5f9899d584c5cfc879d3579352eb929fd4b34756, and the relevant code is unchanged on currentmain@125efa029b4bfd84238bd2c4fd69c3406f802663) - Python version: 3.13.15
- Operating system: macOS 26.6
- Model and model provider: reproduced with
agents.testing.ScriptedModel(no network, no API keys) — the payload is built in the runner before any provider serialization, so this is provider-agnostic - Does the issue reproduce with the latest Agents SDK release? Yes,
0.23.1 - Does the issue occur consistently or intermittently? Consistently
Repro steps
Minimal, self-contained, no network or API keys needed (agents.testing.ScriptedModel drives the run):
import asyncio
import json
from openai.types.responses import ResponseFunctionShellToolCall, ResponseOutputMessage, ResponseOutputText
from agents import Agent, Runner
from agents.testing import ScriptedModel
from agents.tool import ShellResult, ShellTool
def empty_output_executor(request):
# Executor for a command that succeeded and printed nothing.
return ShellResult(output=[])
def shell_call(call_id="call_shell"):
return ResponseFunctionShellToolCall(
id="sc1",
call_id=call_id,
type="shell_call",
action={"type": "shell_command", "commands": ["true"]},
status="completed",
)
async def main():
model = ScriptedModel(
[
[shell_call()],
[
ResponseOutputMessage(
id="m",
type="message",
role="assistant",
content=[
ResponseOutputText(text="done", type="output_text", annotations=[], logprobs=[])
],
status="completed",
)
],
]
)
agent = Agent(name="t", model=model, tools=[ShellTool(executor=empty_output_executor)])
result = await Runner.run(agent, input="run true", max_turns=5)
for item in result.new_items:
if type(item).__name__ == "ToolCallOutputItem":
print("MODEL-VISIBLE structured output:", json.dumps(item.raw_item["output"]))
print("PERSISTED shell_output:", json.dumps(item.raw_item.get("shell_output")))
print("final_output:", repr(result.final_output))
asyncio.run(main())
Actual output
MODEL-VISIBLE structured output: [{"stdout": "(no output)", "stderr": "", "outcome": {"type": "exit", "exit_code": 0}}]
PERSISTED shell_output: [{"stdout": "(no output)", "stderr": "", "status": "completed", "outcome": "success"}]
final_output: 'done'
The item the next model turn receives (ToolCallOutputItem.to_input_item()):
{"type": "shell_call_output", "call_id": "call_shell", "output": [{"stdout": "(no output)", "stderr": "", "outcome": {"type": "exit", "exit_code": 0}}]}
Expected behavior
The executor returned a structured result with no output entries, so the model-visible output should be [] and no shell_output entry should be persisted — the same faithful pass-through the sibling branches of this code path already produce:
ShellResult(output=[ShellCommandOutput(stdout="", stderr="")])→ model-visible{"output": [{"stdout": "", "stderr": "", "outcome": {"type": "exit", "exit_code": null}}]}— the empty stdout is passed through verbatim, nothing fabricated (verified by running the repro with this executor)- executor returning the plain string
""→ model-visible{"output": []},shell_outputabsent (verified the same way)
Three executor shapes that all mean "no output" are handled inconsistently inside one function, and only ShellResult(output=[]) gets a fabricated stdout.
Concrete basis:
render_shell_outputs(src/agents/run_internal/tool_execution.py:969-972) documents"(no output)"as its placeholder: "Render shell outputs into human-readable text for tool responses. / if not outputs: return "(no output)"". It is rendering text for theoutputstring, not executor data, yetShellAction.executelaunders it into the structured channel as real stdout.serialize_shell_output(src/agents/run_internal/tool_execution.py:936-937) is documented as "Serialize ShellCommandOutput for persistence or cross-run transmission" — the persistedshell_outputentry in the bug case serializes nothing the executor produced.ShellExecutor(src/agents/tool.py:1432-1433) is "Executes a shell command sequence and returns either text or structured output" —ShellResult(output=[])is within the documented public contract, andserialize_shell_outputover[]atsrc/agents/run_internal/tool_actions.py:578already produces the faithful payload[].- The repo's own test
test_shell_tool_structured_output_is_rendered(tests/test_shell_tool.py:226) asserts thatraw_item["output"][0]["stdout"]mirrors the executor's stdout, andToolCallOutputItem.to_input_item(src/agents/items.py:471-489) confirmsraw_item["output"]is what the model receives.
Root cause
In ShellAction.execute (src/agents/run_internal/tool_actions.py:608), the truthiness check if shell_output_payload: treats the valid empty payload computed at tool_actions.py:578 as absent, so control falls into elif output_text: (tool_actions.py:610-618), which wraps the human-readable placeholder output_text (from render_shell_outputs, tool_execution.py:969-972) into a structured entry as if it were executor stdout, and that entry is persisted at tool_actions.py:632. The two branches do not distinguish "executor returned a structured ShellResult (possibly empty)" from "executor returned a plain string".
Happy to be assigned. For a fix direction: gate the elif output_text: fallback on whether the executor returned a structured ShellResult (e.g. a flag set in the isinstance(result, ShellResult) branch at tool_actions.py:562), so an empty-but-valid structured result yields output: [] exactly like the plain-empty-string sibling branch — I verified locally that this change makes the repro produce output: [] with no shell_output entry, leaves the two sibling cases unchanged, and passes all 22 tests in tests/test_shell_tool.py.
Related issues / PRs
I searched and found no existing report of this ("no output" shell, ShellResult, shell_output, ShellTool issues/PRs). The only open PR touching src/agents/run_internal/tool_actions.py is #5332 ("fix: preserve completed tool outputs when a sibling fails"), which is about sibling tool failures and unrelated to this fabrication.
- Ngôn ngữ chính
- Python
- Star
- 30k
- Fork
- 4.9k
- Merge trung bình
- 15 giờ 43 phút
- Pull request đã merge (30 ngày)
- 211
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của openai/openai-agents-python
-
enhancement
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 35/100
openai/openai-agents-python#5383 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Tool-calling runs get quadratically slower: completed tool calls are re-fingerprinted every turnĐang mở
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 32/100
openai/openai-agents-python#5382 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Support the OpenAI Decisions APIĐang mở
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
openai/openai-agents-python#5353 · 1 bình luận · 1 reaction ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 Nửa ngày Mức phù hợp với người mới 70/100
openai/openai-agents-python#5342 · 4 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 65/100
openai/openai-agents-python#5336 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của openai/openai-agents-python
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
omegaup/omegaup#10353 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 5 ngày
-
bot-triaged module: LrScheduler module: optimizer triaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
Maintainer thường phản hồi trong vòng 1 ngày
-
documentation glm5 llm-model
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
vllm-project/vllm-ascend#19062 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Author Page: shuai-zhao-llmĐang mởauthor-page correction
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
acl-org/acl-anthology#10496 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày