fix(api): fail-closed disagreeing chat mode aliases - #670
Conversation
The first-wins or-chain hid mode=conduct behind orchestration=route and billed a route chat.completion. Validate each alias on its own so mixed route+conduct is invalid_mode. JSON null and empty string stay omit. Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Warning Review limit reached
Next review available in: 35 minutes Limit details: You’ve used all 1 included review currently available under your plan. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Keep this PR as the orchestration-path landing from main. The no-tools chat path now fail-closes when orchestration / orchestration_mode / mode disagree, so orchestration=route plus mode=conduct is invalid_mode with no choices instead of a billed Fugu route. JSON null/"" stay omit; whitespace-only stays invalid. That is the buyer-facing honesty fix.
Next action for the author: add two locks on this head, then wait for Full unit and contract suite and Semgrep before asking an independent human reviewer to APPROVE.
- In
fuzz/targets.py, after a successful_resolve_requested_chat_mode, if two of the three aliases are present, non-omit, and resolve to distinctALLOWED_MODESmembers, the seam must have raisedRequestError. Today's call only checks “allowed or error” — a first-wins revert still passes fuzz. Also shapeorchestration/orchestration_modein the Hypothesis structured strategy. - Add one live HTTP case that sends
orchestration_mode=routeplusmode=conductand expects 400invalid_mode. The current suite never sends the third alias.
Do not fold tools/response_format passthrough into this head — that remains #647 / #640. If the new docs stay as “mixed aliases are invalid_mode on /v1/chat/completions”, add one sentence that passthrough still ignores mode aliases.
Do not open a second agree-aliases PR from main. Independent non-author APPROVE is still required. CodeRabbit on this head is SUCCESS / “Review rate limited”; CLI agent auth timed out. Neither is a merge blocker.
Sent by Cursor Automation: Fix Issues
|
|
||
| # Per-key alias resolution: mixed orchestration/mode values must agree or | ||
| # raise RequestError — never pick the first truthy alias. | ||
| try: |
There was a problem hiding this comment.
This call does not lock fail-closed. After a successful resolve, if two of orchestration / orchestration_mode / mode are present, non-omit, and _validate_mode yields distinct members of ALLOWED_MODES, this must have raised RequestError. A first-wins revert still returns an allowed mode and stays green.
Next action: assert the disagreeing-alias case raises here, and add those two keys to the Hypothesis structured strategy in tests/fuzz/test_fuzz_properties.py.
| return server, thread, server.server_address[1] | ||
|
|
||
|
|
||
| def test_http_chat_rejects_mixed_route_and_conduct_aliases() -> None: |
There was a problem hiding this comment.
This locks orchestration + mode only. Dropping orchestration_mode from the resolver loop would still keep this suite green.
Next action: add one HTTP case with orchestration_mode=route and mode=conduct → 400 invalid_mode and no choices. Optional reverse pair: orchestration=conduct + mode=route.
| return | ||
| messages = _validate_messages(body.get("messages")) | ||
| mode = _validate_mode(body.get("orchestration") or body.get("orchestration_mode") or body.get("mode") or "auto") | ||
| mode = _resolve_requested_chat_mode(body) |
There was a problem hiding this comment.
This is the no-tools path only. tools / response_format / function_call still return above this line via PASSTHROUGH_TRIGGER_KEYS, so mixed aliases on that path stay 200 single-agent proxy. That does not reopen route-hiding-conduct (passthrough never conducts).
Next action: leave #647 / #640 as the passthrough landing. If README / docs/rest_api_design.md keep the blanket invalid_mode wording, add one sentence that passthrough ignores mode aliases.
| for key in ("orchestration", "orchestration_mode", "mode"): | ||
| if key not in body: | ||
| continue | ||
| raw_mode = body.get(key) |
There was a problem hiding this comment.
null and "" omit is right for SDK optional defaults. Whitespace-only correctly fails in _validate_mode (not in ALLOWED_MODES). Keep that split; do not strip() into omit, or orchestration=route plus mode=" " becomes a silent route again.
|
Merge loop: exact remaining blocker is queued required checks on head |
Pull request was closed


Buyer next action
On
/v1/chat/completions, send one oforchestration/orchestration_mode/mode, or omit them. Mixedorchestration=routeplusmode=conductis nowinvalid_modeinstead of a billed routechat.completion. JSONnulland empty string stay omit-equivalent. Do not send whitespace-only mode.Why this PR exists
On main, the chat path used a first-wins or-chain. Live HTTP: no-tools
orchestration=routeplusmode=conductbilled route. A buyer who asked for a Conductor workflow must not receive a silent Fugu-style single-worker route (Nielsen et al., 2025; Xu et al., 2025).This unique is from main, not the #613/#647 stack. #647 and #640 still own tools-passthrough per-key checks.
Independent non-author APPROVE + Full unit + Semgrep required. Do not self-approve.
Test plan
References