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

Finished tool call is dropped when a parallel tool's guardrail trips

Aperta
#5,327 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@jbeckwith-oai ci sta già lavorando.

Dal 7/10/2026.

  • #5332 di @jbeckwith-oai — aperta

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
35/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Ferma
Stack tecnologico
python

Direzione di ricerca

The report includes a complete ScriptedModel repro but does not name implementation or test files. Start by running the repro and tracing how parallel tool results are handled when the batch raises; check how run_data, session state, and input-list conversion retain completed calls and preceding reasoning items. Done means the run still raises while completed calls remain available to the next turn; an open pull request is already linked.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Please read this first
  • Have you read the docs? Yes.
  • Have you searched for related issues? Yes. #4125 and #5318 fixed similar losses, but this case still happens on main.
Describe the bug

When parallel tool calls run and one of them ends the run with an exception, a call that already finished is dropped from the history.

For example, the model calls create_ticket and send_email together. create_ticket finishes. Then a tool input guardrail on send_email trips, and the run raises ToolInputGuardrailTripwireTriggered. The Session ends up with only the user message, so on the next turn the model creates the ticket again. gpt-5-mini did this 10 of 10 times. With a fix, it was 0 of 10.

The input was accepted and create_ticket really ran, so this isn't speculative work. The guardrails docs keep "already-completed tool call and tool output items" when an output guardrail trips. The same loss also happens with a tool output guardrail, with failure_error_function=None, and when on_tool_end raises.

A fix that worked for me: when the tool batch raises, record the outputs of calls that already finished before re-raising, and keep any reasoning item that comes before a kept call.

Debug information
  • Agents SDK version: 0.23.1, and main at 71c2da4d
  • Python 3.12.13, macOS 15.6.1, openai 3.24.0
  • Model: none, the repro uses ScriptedModel
  • Reproduces on the latest release, every time
Repro steps
import asyncio

from agents import (Agent, RunHooks, Runner, SQLiteSession, ToolGuardrailFunctionOutput,
                    function_tool, set_tracing_disabled, tool_input_guardrail)
from agents.testing import ScriptedModel, assistant_message, function_call

ticket_created = asyncio.Event()


@function_tool
def create_ticket(order_id: str) -> str:
    return f"ticket T-1 for {order_id}"  # The ticket now exists.


@tool_input_guardrail
async def block_email(data) -> ToolGuardrailFunctionOutput:
    await ticket_created.wait()  # Trip only after create_ticket has finished.
    return ToolGuardrailFunctionOutput.raise_exception(output_info="blocked")


@function_tool(tool_input_guardrails=[block_email])
def send_email(order_id: str) -> str:
    return "sent"


class Hooks(RunHooks):
    async def on_tool_end(self, context, agent, tool, result) -> None:
        ticket_created.set()


def show(items: list) -> list[str]:
    return [f"{i['type']}:{i['call_id']}" if "call_id" in i else i.get("role", "?") for i in items]


async def main() -> None:
    calls = [function_call(n, {"order_id": "A1"}, call_id=f"call_{n}") for n in ("send_email", "create_ticket")]
    model = ScriptedModel([calls, [assistant_message("ok")]])
    agent = Agent(name="support", model=model, tools=[send_email, create_ticket])
    session = SQLiteSession("demo")
    try:
        await Runner.run(agent, "Open a ticket for order A1 and email the customer.", session=session, hooks=Hooks())
    except Exception as exc:
        print("raised:      ", type(exc).__name__, "| run_data.new_items:", len(exc.run_data.new_items))
    print("session:     ", show(await session.get_items()))
    await Runner.run(agent, "Please finish the task.", session=session)
    print("next request:", show(model.last_call.input))


set_tracing_disabled(True)
asyncio.run(main())

Output on 0.23.1 and on main:

raised:       ToolInputGuardrailTripwireTriggered | run_data.new_items: 0
session:      ['user']
next request: ['user', 'user']
Expected behavior

The run still raises, but the finished call stays in the Session, run_data, to_input_list() and to_state(), so the next turn sees it:

raised:       ToolInputGuardrailTripwireTriggered | run_data.new_items: 2
session:      ['user', 'function_call:call_create_ticket', 'function_call_output:call_create_ticket']
next request: ['user', 'function_call:call_create_ticket', 'function_call_output:call_create_ticket', 'user']

A reasoning item before a kept call has to stay too, or the Responses API rejects the next request.

I ran the repro on 0.23.1 and on main.

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

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.