Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

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

Aperta Adatta ai principianti
#5,348 5 commenti 0 reazioni 0 assegnatari Vedi su GitHub

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
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
python
Ambito
tooling

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

Lingua principale
Python
Stelle
30k
Fork
4.9k
Merge medio
13h 57m
PR unite (30g)
208

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di openai/openai-agents-python

Tutte le issue di openai/openai-agents-python

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.