Skip to content

Fix response format race in AI binding's run(). - #8204

Open
nick-tsiones wants to merge 1 commit into
cloudflare:mainfrom
nick-tsiones:fix/ai-options-race
Open

nick-tsiones wants to merge 1 commit into
cloudflare:mainfrom
nick-tsiones:fix/ai-options-race

Conversation

@nick-tsiones

Copy link
Copy Markdown

Fixes #8203

What was wrong

Ai.run() in src/cloudflare/internal/ai-api.ts stored its per-call options in a single shared #options field, and after awaiting the upstream fetch it read that field again to decide whether to return the raw Response or a parsed object/stream. When another run() call started while the first was still in flight, the first call formatted its response according to the second call's options: a returnRawResponse: true call could receive a parsed object (breaking access to res.body for streaming), and a plain call could receive an unexpected Response.

All other reads of #options (extra headers, prefix, signal, and the websocket branch) happen synchronously before the fetch is started, so the post-await format decision was the only read that could interleave with another call.

What changed

run() now decides the response format from its own options argument instead of the shared slot. One functional line, plus a comment explaining why the shared slot must not be read there, so it doesn't get "simplified" back.

Compatibility

No compatibility flag: only callers issuing concurrent run() calls observe a difference, and for them the previous behavior depended on timing — the old outcome is not something code could sanely depend on. This decision is recorded in the commit message as well.

Tests

  • delayedModel in ai-mock.js holds its response for 100 ms so another run() call can start and finish in the meantime (modeled on the existing hangingModel).
  • Two regression cases in ai-api-test.js, one per interleaving: the raw-response run starting first and finishing last, and starting last and finishing first.

Both fail on main and pass with this change. Verified by executing the real ai-api.ts under Node with a stubbed fetcher: pre-fix, the returnRawResponse call resolved to a parsed Object; post-fix, each concurrent call gets its own format. prettier --check (repo config) passes on all three changed files.

bazel test //src/cloudflare/internal/test/ai:ai-api-test@ \
  //src/cloudflare/internal/test/ai:ai-api-test@all-compat-flags \
  //src/cloudflare/internal/test/ai:ai-api-test@all-autogates

I prepared this PR with an AI coding agent (OpenCode). I am responsible for it and will answer review comments.

run() stored its per-call options in a single shared #options field, and
after awaiting the fetch it read that field again to decide whether to
return the raw Response or a parsed object/stream. When another run()
call started while the first was still in flight, the first call could
format its response according to the second call's options, e.g. a
call with returnRawResponse set could get a parsed object instead of
the Response it needs to stream.

Decide the response format from the call's own options instead. The
remaining reads of #options all happen synchronously before the fetch
is started, so they cannot interleave with another call.

No compatibility flag: only callers issuing concurrent run() calls
observe a difference, and for them the old behavior was a race whose
result depended on timing, not something to preserve.

Adds a delayedModel to ai-mock.js and two regression tests covering
both interleavings of concurrent plain and returnRawResponse runs.
@nick-tsiones
nick-tsiones requested review from a team as code owners November 3, 2026 23:57
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@clanky-the-clanker

Copy link
Copy Markdown

This pull request was not reviewed automatically because its author does not have write access to this repository. A maintainer with write access can start a review by commenting clanky review (or clanker review or @clanky-the-clanker review), optionally followed by instructions (e.g. clanky review focus on the retry logic).

@nick-tsiones

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Nov 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug Report — Runtime APIs - concurrent env.AI.run() calls can return responses in the wrong format

1 participant