ShellTool: empty ShellResult(output=[]) fabricates stdout "(no output)" in the model-visible shell_call_output and persisted shell_output
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 77/100
Direzione di ricerca
Inizia da src/agents/run_internal/tool_actions.py, in ShellAction.execute, dove il payload serializzato vuoto ricade sul fallback del testo renderizzato. Leggi i rami adiacenti di ShellResult e delle stringhe semplici, poi esegui tests/test_shell_tool.py e la riproduzione fornita con ScriptedModel. Il risultato atteso è che un risultato strutturato vuoto produca output: [] senza salvare alcuna voce shell_output, mentre i casi adiacenti continuano a superare i test.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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.
- Lingua principale
- Python
- Stelle
- 30k
- Fork
- 4.9k
- Merge medio
- 13h 57m
- PR unite (30g)
- 208
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di openai/openai-agents-python
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 85/100
openai/openai-agents-python#5387 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 52/100
openai/openai-agents-python#5386 ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement
Difficoltà 4/5 3-5 giorni Idoneità per principianti 35/100
openai/openai-agents-python#5383 ·
I maintainer di solito rispondono entro 1 giorno
-
Tool-calling runs get quadratically slower: completed tool calls are re-fingerprinted every turnAperta
Difficoltà 4/5 3-5 giorni Idoneità per principianti 32/100
openai/openai-agents-python#5382 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
openai/openai-agents-python#5353 · 1 commento · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di openai/openai-agents-python
Issue simili
-
bug Size: XS
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
oposs/repo-infra#93 ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 69/100
Graphify-Labs/graphify#4546 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 65/100
521xueweihan/HelloGitHub#4025 ·