Repository navigation
[Bug]: parallelToolCalls=false only sends parallel_tool_calls:false for the NVIDIA NIM baseUrl; ignored for all other opted-out providers #1650
Description
Client or integration
Codex App
Area
Proxy and routing
Summary
provider.parallelToolCalls === false only actually sends parallel_tool_calls: false on the wire when the provider's baseUrl is exactly https://integrate.api.nvidia.com/v1. Every other OpenAI-compatible provider that sets parallelToolCalls: false in its config silently gets no wire bit, so upstreams that default to parallel tool calls keep emitting multiple concurrent tool calls in one streamed turn.
For Kimi-family and some GLM-family gateways, the streamed multi-call turn then can't be assembled cleanly into Responses-shaped function-call events, and Codex fails the turn (previously surfaced as upstream response contained invalid tool calls; after the 2.14 null-tool_calls hardening, still a broken/incomplete multi-tool turn). Setting parallelToolCalls: false — which the config schema advertises as the way to opt out — has no effect for these providers.
What I expected
parallelToolCalls: false should pin parallel_tool_calls: false on the request for any opted-out provider, not just the hardcoded NVIDIA NIM host. The config field reads as provider-agnostic; the enforcement is host-specific.
Evidence (code path)
src/adapters/openai-chat.ts, request builder:
if (provider.parallelToolCalls === false) {
// NIM documents the Boolean defaulting to false and kimi rejects true; pin the
// wire bit so Codex cannot opt in via request.options. Other opted-out providers
// omit the field so strict OpenAI-compatible hosts never see an unsupported knob.
if (provider.baseUrl === "https://integrate.api.nvidia.com/v1") {
body.parallel_tool_calls = false;
}
} else if (provider.parallelToolCalls === true) {
body.parallel_tool_calls = parsed.options.parallelToolCalls !== false;
}The comment's intent ("Other opted-out providers omit the field so strict OpenAI-compatible hosts never see an unsupported knob") is reasonable as a default, but it makes parallelToolCalls: false a no-op for every non-NVIDIA gateway that genuinely needs the wire bit. There's no per-provider way to force it short of matching the NVIDIA URL.
Reproduction
- Configure an OpenAI-compatible
openai-chatprovider (e.g. a Kimi-K3 gateway) with"parallelToolCalls": false. - Send a Codex turn that naturally invites two independent tool calls (e.g. "read file A and list dir B").
- Observe the upstream returns 2 parallel
tool_callschunks in the same choice;parallel_tool_calls: falsewas never sent. - As a control, send the exact same upstream body with
parallel_tool_calls: falseadded manually — the upstream then returns a single tool call.
Proposal (either is fine)
- Send
body.parallel_tool_calls = falsewheneverprovider.parallelToolCalls === false, regardless ofbaseUrl; or - Keep the NVIDIA-only default but add an explicit opt-in (e.g.
provider.forceParallelToolCallsFalse: true) so gateways that reject/ignore parallel calls can pin the bit without a URL match.
Version
opencodex 2.14.2 (ocx --version); running local service confirmed via GET /healthz.
Operating system
macOS 26.5.1 (25F80)
Provider and model
OpenAI-compatible openai-chat routed providers (Kimi-K3, GLM-5.2 families) on a self-hosted gateway. Not NVIDIA NIM. Reproducible with any openai-chat provider whose upstream emits parallel tool calls by default.
Activity
coderabbitai commentedon Aug 14, 2026
🔗 Related PRs
#304 - fix(kiro): restore hardening and complete text turns [open]
#616 - fix: preserve hosted image tool preferences [open]
#746 - fix(providers): route GitHub Copilot Responses-only models off chat completions [open]
#1317 - feat(providers): add Featherless preset [merged]
#1531 - fix(openai-chat): refuse to emit an unnamed streamed tool call [merged]
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
tone-leh commentedon Aug 14, 2026
Note for triage: this is distinct from #1531. #1531 changed how an already-produced unnamed/invalid tool call is terminated (fail closed with a loud error instead of emitting a nameless call). This issue is upstream of that: it is about the parallel tool calls being requested in the first place.
provider.parallelToolCalls === falseis documented as the opt-out, but the wire bitparallel_tool_calls: falseis only actually sent whenprovider.baseUrl === "https://integrate.api.nvidia.com/v1". For every other opted-out OpenAI-compatible provider (e.g. a self-hosted Kimi/GLM gateway) the field is silently omitted, so the upstream keeps emitting multiple concurrent tool calls in one streamed turn. #1531's hardening then makes that turn fail more cleanly, but the turn still fails — the fix here is to stop requesting parallel calls at all for opted-out providers, not to clean up after them.fix(openai-chat): let opted-out providers pin parallel_tool_calls:false
LeoWang331 commentedon Aug 14, 2026
I'm working on honoring
provider.parallelToolCalls === falseon the openai-chat wire for every opted-out provider, not only the NVIDIA NIM baseUrl, and will open a draft PR shortly. Distinct from #1531: this is about requesting serialized tool calls, not fail-closing already-invalid streamed calls.Ingwannu commentedon Aug 14, 2026
Confirmed fixed on
dev. PR #1662 merged ase480ce74803e1acd06d0a1b656101713051ad911and adds the explicitpinParallelToolCallsFalseopt-in while preserving the existing omit-by-default behavior for strict OpenAI-compatible hosts. The focused regression coverage verifies thatparallel_tool_calls: falseis emitted only when bothparallelToolCalls: falseand the new pin are set. Closing as fixed indev; it will reach users with the next maintainer promotion/release.LeoWang331 commentedon Aug 14, 2026
Thanks — I see #1662 already landed proposal 2 (
pinParallelToolCallsFalse) and this is closed as fixed ondev. I will not open a conflicting PR that would pinparallel_tool_calls: falsefor everyparallelToolCalls: falseprovider.fix(openai-chat): let opted-out providers pin parallel_tool_calls:false