ShellTool: empty ShellResult(output=[]) fabricates stdout "(no output)" in the model-visible shell_call_output and persisted shell_output
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 77/100
Línea de trabajo
Empieza en src/agents/run_internal/tool_actions.py, en ShellAction.execute, donde la carga serializada vacía pasa al fallback de texto renderizado. Lee las ramas adyacentes de ShellResult y de cadenas de texto simples; después ejecuta tests/test_shell_tool.py y la reproducción proporcionada con ScriptedModel. El objetivo se alcanza cuando un resultado estructurado vacío produce output: [] sin guardar ninguna entrada shell_output, mientras los casos adyacentes siguen pasando.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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.
- Lenguaje dominante
- Python
- Estrellas
- 30k
- Forks
- 4.9k
- Merge medio
- 18 h 13 min
- PR fusionados (30 d)
- 215
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de openai/openai-agents-python
-
Support the OpenAI Decisions APIAbierto
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
openai/openai-agents-python#5353 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 Medio día Aptitud para principiantes 70/100
openai/openai-agents-python#5342 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
enhancement
Dificultad 3/5 1-2 días Aptitud para principiantes 65/100
openai/openai-agents-python#5336 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Finished tool call is dropped when a parallel tool's guardrail tripsPosiblemente ocupada @jbeckwith-oai la tomó hace 7 días. Abierto
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
openai/openai-agents-python#5327 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 35/100
openai/openai-agents-python#5322 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
Todos los issues de openai/openai-agents-python
Issues similares
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
Deltares/imod-python#1940 ·
Los mantenedores suelen responder en 1 día
-
effort: low prio: low ready
Dificultad 1/5 Menos de una hora Aptitud para principiantes 82/100
digitalfabrik/integreat-cms#4621 ·
Los mantenedores suelen responder en 5 días
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
ioos/compliance-checker#1375 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
Los mantenedores suelen responder en 1 día
-
netbox status: backlog topic: testing type: housekeeping
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
netbox-community/netbox#23395 ·
Los mantenedores suelen responder en 1 día