Skip to content

fix(api): fail-closed disagreeing chat mode aliases - #670

Closed
seonghobae wants to merge 1 commit into
mainfrom
cursor/bc-b41e0d4b-b044-432b-bfd7-a516ef7d8696-4ce7
Closed

fix(api): fail-closed disagreeing chat mode aliases#670
seonghobae wants to merge 1 commit into
mainfrom
cursor/bc-b41e0d4b-b044-432b-bfd7-a516ef7d8696-4ce7

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Buyer next action

On /v1/chat/completions, send one of orchestration / orchestration_mode / mode, or omit them. Mixed orchestration=route plus mode=conduct is now invalid_mode instead of a billed route chat.completion. JSON null and 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=route plus mode=conduct billed 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

  • python3 tests/test_chat_orchestration_mode_http_honesty.py (RED then GREEN)
  • paper/self/conventions/api/product-planning/plugin/admin/security/passthrough contracts
  • CI Full unit + Semgrep

References

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>
@cursor

cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown

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.

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@seonghobae, you've reached your PR review limit, so we couldn't start this review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 15adf390-7fb5-466e-accb-0ebeacbc4078

📥 Commits

Reviewing files that changed from the base of the PR and between 6841b71 and 6cb7baa.

📒 Files selected for processing (9)
  • README.md
  • conductor/tracks.md
  • contextual_orchestrator/server.py
  • docs/architecture.md
  • docs/fuzzing.md
  • docs/papers/README.md
  • docs/rest_api_design.md
  • fuzz/targets.py
  • tests/test_chat_orchestration_mode_http_honesty.py

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

  1. In fuzz/targets.py, after a successful _resolve_requested_chat_mode, if two of the three aliases are present, non-omit, and resolve to distinct ALLOWED_MODES members, the seam must have raised RequestError. Today's call only checks “allowed or error” — a first-wins revert still passes fuzz. Also shape orchestration / orchestration_mode in the Hypothesis structured strategy.
  2. Add one live HTTP case that sends orchestration_mode=route plus mode=conduct and expects 400 invalid_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.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

Comment thread fuzz/targets.py

# Per-key alias resolution: mixed orchestration/mode values must agree or
# raise RequestError — never pick the first truthy alias.
try:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@seonghobae
seonghobae enabled auto-merge (squash) August 16, 2026 17:06
@seonghobae

Copy link
Copy Markdown
Contributor Author

Merge loop: exact remaining blocker is queued required checks on head 6cb7baaface491485c5ab152987f3d29da6b68efFull unit and contract suite, Semgrep (multi-language SAST), strix, noema-review, plus required-workflow-bootstrap / scan-pr-queue. REVIEW_REQUIRED until those are terminal and an independent non-author APPROVE exists. Not waiting on OpenCode/Strix/Noema.

@seonghobae seonghobae closed this Aug 16, 2026
auto-merge was automatically disabled August 16, 2026 18:22

Pull request was closed

@seonghobae

Copy link
Copy Markdown
Contributor Author

Superseded by tip #691 (cumulative honesty substrate ≥ #689). Closing to free Full unit + Semgrep runners.

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.

2 participants