Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

ShellTool: empty ShellResult(output=[]) fabricates stdout "(no output)" in the model-visible shell_call_output and persisted shell_output

Abierto Apto para principiantes
#5,348 2 comentarios 0 reacciones 0 asignados Ver en GitHub

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
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
python
Área
tooling

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_output payload 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 on main @ 5f9899d584c5cfc879d3579352eb929fd4b34756, and the relevant code is unchanged on current main @ 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_output absent (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 the output string, not executor data, yet ShellAction.execute launders 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 persisted shell_output entry 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, and serialize_shell_output over [] at src/agents/run_internal/tool_actions.py:578 already produces the faithful payload [].
  • The repo's own test test_shell_tool_structured_output_is_rendered (tests/test_shell_tool.py:226) asserts that raw_item["output"][0]["stdout"] mirrors the executor's stdout, and ToolCallOutputItem.to_input_item (src/agents/items.py:471-489) confirms raw_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

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de openai/openai-agents-python

Todos los issues de openai/openai-agents-python

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.