DEV Community

Mohammed Rafay
Mohammed Rafay

Posted on Originally published at memorysync.hashnode.dev

OpenAI Agents SDK: 'No tool call found for function call output' after an approval resume

Disclosure: I work on MemorySync (I'm Mohammed, the founder). I drafted this page with AI assistance and checked it against the source thread and the official docs listed under Sources on Oct 28, 2026. I read the issue, the fix PR and the release notes; I didn't run the reproducer for this page.

The question

openai/openai-agents-python#4827 reports that after an approved tool call is resumed, the next Runner.run(..., session=session) fails, and keeps failing:

openai.BadRequestError: Error code: 400 - No tool call found for function call
output with call_id call_ORPHAN
Enter fullscreen mode Exit fullscreen mode

The issue says the Session ends up holding a function_call_output whose function_call was never written, and since that orphan is stored durably, "every subsequent turn fails the same way."

Status (re-checked Oct 29, 2026): closed as completed on Oct 6, 2026, by PR #4828, "fix(sessions): persist deferred approval history across resumes". The fix is listed in the v0.24.0 release notes (Oct 15, 2026). The latest openai-agents on PyPI is 0.24.0.

Skip the memory plumbing around this fix. Once the fix below is in, the MemorySync OpenAI Agents SDK guide shows how to add per-user memory with MemorySync. MemorySync doesn't fix this error itself. When you're ready, sign up and try MemorySync. Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

When it happens

Per the issue, all of these together, on versions since the deferral change shipped in 0.22.0:

  • the agent has output_guardrails, and
  • tool_use_behavior is not the default "run_llm_again" (the reproducer uses StopAtTools), and
  • a needs_approval=True tool parks the run, and
  • after approval, the resumed tool is not a terminal tool, so the run continues.

The interrupted turn's items (the function_call and the approval item) are held back at park time. On that resume path nothing wrote them, so only the tool output landed in the Session. One commenter reproduced the orphan offline on 0.22.3 with ScriptedModel and SQLiteSession.

Fix

  1. Upgrade to openai-agents 0.24.0 or later. That's the release that contains #4828.
  2. Repair sessions already poisoned. Upgrading doesn't rewrite stored history, so an orphan written before the upgrade still makes every request fail. Options, using the SDK's Session methods:
    • start a new session_id for that conversation, or
    • clear_session() if losing that conversation is acceptable, or
    • if the orphan is the last item, pop_item() removes it.
  3. Check before you run (optional). Every function_call_output in await session.get_items() should have an earlier function_call with the same call_id. If not, don't send that history.

How to tell it's this bug

  • The approval resume succeeds, and the failure shows up on the next run.
  • The failing call_id is the one from the approval-gated tool.
  • A brand-new session works; the old one fails on every turn.

The same 400 shows up in other stacks whenever a tool output is replayed without its call (for example a stream aborted mid-turn), so check the session items rather than assuming it's this exact path.

Common questions

Is this a bug in SQLiteSession?
No. The reproducer uses SQLiteSession, but the missing write was in the runner's resume path, so any Session backend received the same incomplete items.

Do I need output guardrails to hit it?
Per the issue, yes: the deferral only applied with output guardrails plus a non-default tool_use_behavior.

Skip the plumbing with MemorySync

It doesn't fix this. The MemorySync OpenAI Agents SDK guide documents MemorySyncSession, which implements the SDK's Session protocol (get_items, add_items, pop_item, clear_session) and returns items "exactly as the SDK wrote them". So it stores whatever the runner hands it, and you'd still upgrade the SDK. What it adds is server-side conversation history keyed by session_id with a required user_id, and user messages sent to fact extraction so durable facts become that user's long-term memories across sessions. Read the guide first. To see save and recall in your browser, try the MemorySync playground.

From the guide:

pip install openai-agents-memorysync openai-agents
Enter fullscreen mode Exit fullscreen mode
from agents import Agent, Runner
from openai_agents_memorysync import MemorySyncSession

agent = Agent(
    name="Assistant",
    instructions="You are a helpful assistant.",
)

session = MemorySyncSession(
    "thread-42",          # the conversation
    user_id="customer-7", # the end user it belongs to — required
)

# First conversation
await Runner.run(agent, "I'm vegetarian and I fly aisle.", session=session)

# Any later run — same session id, any process, any deploy
result = await Runner.run(
    agent, "Book my trip: flight plus a dinner spot.", session=session
)
# The model saw the full prior history — no manual .to_input_list() plumbing.
Enter fullscreen mode Exit fullscreen mode

Snippet adapted from the MemorySync OpenAI Agents SDK guide; I haven't run it for this page.

Sources

  • openai/openai-agents-python#4827 (closed 2026-10-06, completed; re-checked 2026-10-29)
  • openai/openai-agents-python#4828 (merged 2026-10-06)
  • openai-agents-python v0.24.0 release notes (2026-10-15)
  • MemorySync docs: docs.memorysync.io/guides/openai-agents

Next step: read the MemorySync OpenAI Agents SDK guide, then sign up and try MemorySync. Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

Top comments (0)