DEV Community

Karl Ji
Karl Ji

Posted on

Codex + DeepSeek: why "No tool output found for tool call" kills your session (and how to avoid it)

TL;DR

  • Error: No tool output found for tool call call_01_... (HTTP 400, invalid_request_error) in Codex CLI / Codex Desktop with DeepSeek or another strict OpenAI-compatible provider.
  • Cause: Codex batched two view_image calls. After resizing a large image, it inserted an <image_resize_notice> developer message between the two function_call_output items. DeepSeek's Responses API expects each output to directly follow its call, so it rejected the request.
  • Why it never recovers: the broken turn stays in the thread history, so every later message fails with the same call ID.
  • Workaround: make view_image the only tool call in its turn, and pre-shrink screenshots (viewport-only, ≤1500 px wide). Start a new thread to recover.
  • Ready-made rules: I packaged all of this as an agent skill for Codex and Claude Code: https://github.com/Kar23232/unstuck-qa

The error

If you're here, you've probably seen this in Codex:

No tool output found for tool call call_01_HcX4jELNhOOdX0zqGayn0609.
Enter fullscreen mode Exit fullscreen mode

It usually appears right after the agent says something like "Viewed 2 images". After that, every message you send returns the exact same error.

How I ran into it

I use Codex Desktop with DeepSeek as the model provider (model_provider = "deepseek", wire_api = "responses"). I was doing QA work: rendering document pages to PNG to check the layout, and screenshotting a web app to reproduce bugs.

Mid-task, the agent looked at two images at once. Then:

No tool output found for tool call call_01_UISHMq0wlN4CLWt3E6Wa2694.
Enter fullscreen mode Exit fullscreen mode

I typed "?". Same error. I typed something much less polite. Same error, same call ID. The thread was dead.

It happened in three separate threads: twice on September 17 and once on September 21, 2026.

My first guess, and the first explanation an AI assistant gave me, was that the provider was dropping image outputs, or that the image was too big. The image was only 91 KB, so size seemed ruled out. Both guesses were wrong.

Reading the rollout logs

Codex saves every thread as JSONL under:

~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl
Enter fullscreen mode Exit fullscreen mode

Search for the failing call ID to find the right file:

grep -rl "call_01_HcX4" ~/.codex/sessions
Enter fullscreen mode Exit fullscreen mode

Here's the timeline around the failure (timestamps in seconds, image payloads removed):

26.172  function_call          view_image  call_00_…  page-1.png
26.350  function_call          view_image  call_01_…  page-2.png
26.376  function_call_output              call_00_…
26.377  message (developer)    <image_resize_notice> …
26.382  function_call_output              call_01_…
26.382  message (developer)    <image_resize_notice> …
26.745  ❌ 400 No tool output found for tool call call_01_…
Enter fullscreen mode Exit fullscreen mode

Three things stood out, and they were identical in all three dead threads:

  1. Both outputs exist. Nothing was lost. call_01 has its function_call_output.
  2. A developer message sits between them. Codex inserted an <image_resize_notice> right after output #1, so it landed between output #1 and output #2.
  3. It always fails on the second call (call_01_…), never the first.

About the 91 KB: resizing depends on pixel dimensions, not file size. A small PNG of a tall rendered page can still go over the limit. CodexPlusPlus#2275 mentions images over roughly 2048 px or about 2.5 million pixels.

Root cause: strict adjacency meets an inserted notice

In the Responses API, a conversation is a list of items: function_call, function_call_output, message, reasoning, and so on.

  • OpenAI's Responses API pairs a function_call with its function_call_output by call_id, wherever they sit in the list (as described in deepseek-ai/awesome-deepseek-integration#740).
  • DeepSeek's Responses-compatible endpoint requires the output to come directly after its call. Issue #740 reproduces this cleanly: put a developer message or a reasoning item between a call and its output and you get the 400; keep them adjacent and you get 200.

Now add Codex's resize behavior. When an image in a tool output gets resized, Codex appends an <image_resize_notice> developer message after that output.

✅ One image: fine                    ❌ Two images in parallel: rejected
function_call          A              function_call          A
function_call_output   A              function_call          B
developer: resize notice              function_call_output   A
                                      developer: resize notice   ← breaks adjacency
                                      function_call_output   B   ← "No tool output found"
Enter fullscreen mode Exit fullscreen mode

With one image, the notice comes after the output and nothing breaks. With two parallel images, the notice for image A lands between the two outputs, and DeepSeek decides call_01 was never answered.

Why the thread is wedged forever: Codex resends the full history on every turn, so the same malformed sequence goes out every time and gets the same 400. Resuming the thread replays the same history, so it fails too.

A second path to the same error

The resize notice isn't the only trigger. Other people have documented related paths:

  • openai/codex#44604: the next request is sent before the last tool output of a batch is appended. Reported when view_image is batched with more images (about a 70 ms gap) or with a command that exceeds the 30-second tool wait.
  • farion1231/cc-switch#7569: the proxy's media-lift feature inserts a synthetic user message mid-batch when view_image runs in parallel with exec_command or MCP tools.
  • BigPizzaV3/CodexPlusPlus#2275: when translating to Chat Completions, the resize notice ends up between tool messages, and the upstream rejects the request with Messages with role 'tool' must be a response to a preceding message with 'tool_calls'.

The common thread: an image sharing a batch with other tool calls, plus a provider that enforces strict ordering.

Are you hitting this? Quick checklist

  • ✔️ Codex CLI or Codex Desktop with DeepSeek, or another OpenAI-compatible provider or proxy
  • ✔️ The failing ID looks like call_01_… (the second call of a batch)
  • ✔️ It happened right after "Viewed 2 images", or after view_image ran alongside another tool
  • ✔️ Every later message fails with the same call ID

If most of these match, it's very likely this bug.

Workarounds

1. Make images go alone. view_image should be the only tool call in its turn: never two images at once, and never an image batched with a command. You can put this in AGENTS.md:

- Never call view_image in parallel with any other tool call. One image per turn.
- Before viewing an image, downscale it to ≤1500 px wide. Use viewport screenshots, not full-page captures.
Enter fullscreen mode Exit fullscreen mode

2. Shrink images before the agent looks at them, so Codex never needs to insert a resize notice:

# macOS (limits the longest side to 1500 px)
sips -s format jpeg -Z 1500 shot.png --out shot.jpg

# ImageMagick (only shrinks if wider than 1500 px)
magick shot.png -resize '1500x>' shot.jpg
Enter fullscreen mode Exit fullscreen mode

For very tall pages, take several viewport screenshots instead of one full-page capture.

3. Recover a wedged thread. Start a new thread; retrying and resuming both replay the broken history. To avoid losing work, have the agent keep a progress.md that records what it has finished, so the new thread can continue from there. A more surgical option (described in cc-switch#7569) is to delete the orphaned call/output items from the rollout JSONL. Back the file up first, since this is unofficial.

4. Or switch providers. According to #740, OpenAI's Responses API pairs by call_id, so this ordering shouldn't break there. I haven't tested that myself.

I turned this into a skill: unstuck-qa 🔓

After losing three threads, I wrote the rules down as an agent skill: unstuck-qa. It works in both Codex and Claude Code.

On top of the two rules above, it covers the other ways an agent gets stuck while testing web apps in a real browser:

  • 💬 Native dialogs: registers a confirm() / alert() handler before clicking, so clicks don't hang forever
  • ⏱️ No networkidle waits: waits for specific elements with timeouts, which matters on pages that poll in the background
  • 💾 One click, one verdict for save actions: no endless "refresh and try again" loops
  • 📒 A progress log, so a dead thread doesn't mean starting over
  • 🧪 Skip the UI when possible: unit or API checks take about 10× fewer tool calls than clicking around

Install:

# Codex
git clone https://github.com/Kar23232/unstuck-qa ~/.codex/skills/unstuck-qa

# Claude Code
git clone https://github.com/Kar23232/unstuck-qa ~/.claude/skills/unstuck-qa
Enter fullscreen mode Exit fullscreen mode

What would fix it properly

  • Codex: emit resize notices after the whole batch of outputs, and validate call/output pairing right before sending (see openai/codex#46193).
  • Proxies (cc-switch, CodexPlusPlus and similar): reorder items so every output directly follows its call before forwarding.
  • DeepSeek: pair calls and outputs by call_id, the same way OpenAI's Responses API does (#740).

Related issues

Wrapping up

If your Codex thread keeps answering every message with No tool output found for tool call, nothing is wrong with your files. A notice got wedged between two tool results, and the thread will never recover on its own. Start fresh, keep images alone, shrink them first, and you shouldn't see it again.

If this saved your afternoon, a ⭐ on unstuck-qa helps the next person find it. Found another trigger? Open an issue there.

Top comments (0)