Skip to content

[WRONG BRANCH] fix(effort): run wire mapper on unpinned/unconfigured effort values - #5539

Draft
Haven2026 wants to merge 103 commits into
lidge-jun:devfrom
Haven2026:fix/effort-wire-normalize
Draft

Haven2026 wants to merge 103 commits into
lidge-jun:devfrom
Haven2026:fix/effort-wire-normalize

Conversation

@Haven2026

@Haven2026 Haven2026 commented Sep 22, 2026

Copy link
Copy Markdown

Summary

Two call sites gate the wire-level effort sanitizer (mapReasoningEffort) on whether a rewrite was triggered (pin/cap) or a ladder was configured. On routes with neither, caller-supplied effort spellings outside the Codex ladder pass through verbatim and hit strict upstream enum validators — observed as 400 Invalid option: expected one of "low"|"medium"|"high"|"xhigh"|"max" on a DeepSeek-fronted gateway channel.

Root cause — two places, same mistake

1. src/server/chat-native.ts (normalizePinnedChatEffort, native /v1/chat/completions)

normalizeForWire only becomes true when a pin or cap rewrote the value. With neither configured, chatBody.reasoning_effort is forwarded with the caller's spelling.

2. src/adapters/openai-responses/reasoning.ts (mapRoutedResponsesReasoningEffort, Responses pipeline)

configuredReasoningEfforts(provider, modelId) === undefined returns early, forwarding reasoning.effort verbatim for providers with no configured ladder (e.g. a custom openai-responses provider proxying a gateway).

Why this is a bug, not a config gap

mapReasoningEffort already implements the correct semantics for the unconfigured case: identity for valid ladder spellings, minimallow, ultramax (the upstream codex-rs boundary), and drop for unknown values. The gates skip this sanitizer precisely where it is needed — custom providers whose upstream validators are stricter than the client vocabulary (Claude Code's output_config.effort legitimately emits a 7-value domain incl. minimal/ultra).

Fix

Run the wire mapper whenever an effort value is present, independent of pin/cap/ladder — with three passthrough exemptions confirmed by the existing test suite:

  • "none" — a caller thinking-disable instruction adapters translate to thinking:{type:"disabled"} (absent ≠ disabled);
  • "enabled"/"disabled"/"adaptive" — provider-wire values consumed by thinkingToggleModels adapters (openai-chat.ts);
  • compaction turns — maintenance traffic keeps its effort untouched, matching the existing pin/cap exemption.

Test changes

Three assertions in tests/codex-integration/model-pinned-effort.test.ts recorded the buggy passthrough ("preserves unqualified caller spelling"). Updated to assert the mapped wire values (ultraenabled via the configured max→enabled wireMap alias, ultramax, lowdisabled via the low→disabled alias), each with a comment noting the old expectation.

Verification

  • bun x tsc --noEmit clean
  • tests/codex-integration/model-pinned-effort.test.ts: 47 pass / 0 fail
  • Live probe against a strict upstream (DeepSeek channel): minimal/ultra 400→200, high/xhigh/none 200, unknown spelling 400→200 (field dropped → upstream default)
  • Risk is low: valid spellings are identity through the mapper; only spellings that would have been rejected get folded/dropped.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Reasoning effort values are now consistently normalized before being sent to supported providers.
    • Common aliases and unknown effort levels are mapped to valid provider-specific values.
    • Special values such as none, enabled, disabled, and adaptive are preserved where supported.
    • Pinned effort and cap adjustments now produce the expected provider-compatible results.

Review readiness checklist

This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:

  • All CI tests are green on my local testing.

  • I pushed my PR to the latest dev commit.

  • I resolved all correct Codex and CodeRabbit findings.

  • My PR is ready for review.

lidge-jun and others added 30 commits August 25, 2026 19:10
merge dev into preview for the v2.33.0-preview.20260825 release (api-usage oracle fix)
…w-2340

[WRONG BRANCH] release: promote dev to preview for v2.34.0-preview.20260827
…w-2350

[WRONG BRANCH] promote dev onto preview for v2.35.0-preview
…w-23600829

[WRONG BRANCH] release: v2.36.0-preview.20260829
`preview` carries its own release-bump commits (`release: v2.36.0-preview.20260829`), which
are not on `dev`, so the promotion cannot fast-forward: `package.json` is the only conflicting
path. Resolved to the version this train publishes, `2.36.0-preview.20260830`, which moves the
`preview` channel forward from the 20260829 tip.

Every other path takes `dev` (`bb6a6fbdf`) unchanged.
…w-23600830

[WRONG BRANCH] promote dev onto preview for v2.36.0-preview.20260830
…w-23800831

[WRONG BRANCH] promote dev onto preview for v2.38.0-preview.20260831
[WRONG BRANCH] promote dev onto preview for v2.39.0-preview.20260901
…w-24000902

release: promote dev to preview for v2.40.0-preview.20260902
…rkflow call (lidge-jun#3262)

Both v2.40.0 release dispatches (33615174183 preview, 33615177849 main) died
at startup_failure: a workflow_call cannot grant its callee more than the
calling job holds, and dev-version-bump.yml's job declares contents+pull-
requests write. lidge-jun#3129 wired the call but never dispatched a release, so this
is its first live run. The caller job now declares exactly the callee's two
permissions; no other job in release.yml gains anything.

Co-authored-by: jun <jun@lidge.dev>
(cherry picked from commit 7ce0ba5)
…w-relfix

release: carry the release.yml permissions fix onto preview for v2.40.0-preview.20260902
Brings the reviewed dev head (81a1fc1) onto preview. The only conflict is
package.json's version, resolved to the preview channel's next version rather
than to either side: preview carried 2.40.0-preview.20260902 and dev carries
2.41.0, and neither is what this publish is.

The 37-commit delta since v2.40.0 is reviewed per-commit in
devlog/_plan/260903_muse_release_train/011_review_ledger.md.
…w-2410-v2

[WRONG BRANCH] release: v2.41.0-preview.20260903
…w-2410-v3

[WRONG BRANCH] release: refresh preview onto the audit-clean dev head for v2.41.0-preview.20260903
v2.41.0 published from main while this promotion was in flight, so the preview
line has to move past it: a 2.41.0-preview version is now BEHIND a released
one, which tests/release-version-line.test.ts refuses and npm would refuse
after it.

dev moved to 2.42.0 in lidge-jun#3354, the automatic post-release bump. Preview follows
the same line at 2.42.0-preview.20260903, which is the conflict resolution
here: neither side's value, because neither is what this publish is.

Also brings the three CI timing-flake fixes from lidge-jun#3351, which is what preview
CI failed on twice at 491ffcf.

Preflight: 2.42.0-preview.20260903 unpublished on npm, no v2.42.0* tag, no
GitHub release, and it moves preview forward from 2.40.0-preview.20260902.
…w-2410-v4

[WRONG BRANCH] release: v2.42.0-preview.20260903
…w-2420-badges

[WRONG BRANCH] release: promote dev onto preview for the main-account badge fix
…view

preview had diverged from main: it carried 28 merge commits main did not have,
while main carried 23 preview did not, so preview could not fast-forward.
The divergence was history only - the sole CONTENT difference between the two
branches was the package.json version line.

This merge takes main (48f8186, the v2.42.0 release commit) into preview and
resolves the tree to main's content, so preview now matches the released tree
exactly apart from its own prerelease version line.

The version could not stay at 2.42.0-preview.20260903: v2.42.0 has published,
and compareReleaseTags ranks that prerelease BEHIND its own stable release
(-1), which is what tests/release-version-line.test.ts fails on. preview opens
the next prerelease line at 2.43.0-preview.20260904, matching dev's 2.43.0.
…w-2430

[WRONG BRANCH] chore(release): promote main v2.42.0 onto preview and open 2.43.0-preview
lidge-jun and others added 13 commits September 14, 2026 03:28
…review-20260914

[WRONG BRANCH] release: promote verified 2.54.0-preview.20260914 product tree to preview
Promotes the dev product snapshot 62f0222 to the preview train.

The 2.55.0 line carries the lidge-jun#4546 cost-guard work: one send budget per logical request with a
shared final-recovery reserve, zero-is-zero refusals with a typed error rather than a synthetic
502, compact and the Kiro inner retries admitted against that budget, a finite send ceiling per
root workflow with an interactive reserve a fan-out cannot take, and a healthy detour promoted on
transient-hold expiry instead of released cold.

The previous preview tip 2.54.0-preview.20260914 is already tagged and published and is outranked
by v2.54.0, so it could not be re-released; this is a new candidate rather than a re-cut.
…-2550

[WRONG BRANCH] chore(release): promote 2.55.0-preview.20260914 to preview
Same product tree as preview 7bdd1b2 / 2.55.0-preview.20260914, which published successfully with its registry smoke green. Only package.json version differs.
chore(release): promote the verified 2.55.0 product tree to main
…60-v2

[WRONG BRANCH] release: promote the verified 2.56.0 product tree to main
[WRONG BRANCH] chore(release): promote 2.57.0 to main
[WRONG BRANCH] release: promote dev to main for 2.58.0
release: promote the verified 2.60.0 tree to main
reasoning_effort caller spellings outside the Codex ladder ("minimal",
"ultra") leaked verbatim to strict upstream enum validators whenever no
pin/cap rewrite fired (chat-native) or the provider had no configured
ladder (openai-responses passthrough) — observed as upstream 400
"Invalid option" on a DeepSeek-fronted gateway.

- chat-native: normalizeForWire now true whenever reasoning_effort is
  present (not only after pin/cap), with passthrough kept for "none"
  (caller thinking-disable instruction), "enabled"/"disabled"/
  "adaptive" (thinkingToggleModels wire values), and compaction turns
  (maintenance exemption, same as pin/cap).
- openai-responses/reasoning: the configuredReasoningEfforts===undefined
  branch now runs mapReasoningEffort instead of returning early, folding
  minimal->low / ultra->max and dropping unresolvable ranks.
- tests: three assertions in model-pinned-effort.test.ts recorded the
  buggy passthrough; updated to the mapped wire values with comments.
@coderabbitai

coderabbitai Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 6de6c737-9a33-4db7-bd96-ae692058d47c

📥 Commits

Reviewing files that changed from the base of the PR and between b007d33 and eb623d4.

📒 Files selected for processing (3)
  • src/adapters/openai-responses/reasoning.ts
  • src/server/chat-native.ts
  • tests/codex-integration/model-pinned-effort.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Changes

Reasoning effort normalization

Layer / File(s) Summary
Provider effort mapping
src/adapters/openai-responses/reasoning.ts
mapRoutedResponsesReasoningEffort now normalizes efforts for unconfigured providers. It maps supported aliases, removes unknown values, and preserves downstream provider spellings.
Native chat normalization and validation
src/server/chat-native.ts, tests/codex-integration/model-pinned-effort.test.ts
Non-compaction native chat turns now normalize eligible caller values before dispatch. Tests verify ultra, max, and low wire mappings.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant NativeChat
  participant normalizePinnedChatEffort
  participant mapRoutedResponsesReasoningEffort
  participant Provider
  NativeChat->>normalizePinnedChatEffort: caller reasoning_effort
  normalizePinnedChatEffort->>mapRoutedResponsesReasoningEffort: eligible effort
  mapRoutedResponsesReasoningEffort->>Provider: normalized wire value
Loading

Merge Risk: ⚪ Minimal · up to eb623

The change normalizes eligible reasoning-effort values while preserving documented exceptions, with updated expectations for the resulting provider values. It is ready to merge.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: applying wire-level effort mapping to unpinned and unconfigured effort values. It matches the changes in the native chat and OpenAI Responses routes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🧪 Generate unit tests (beta)
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

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.

@github-actions github-actions Bot added the bug Something isn't working label Sep 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot changed the title fix(effort): run wire mapper on unpinned/unconfigured effort values [WRONG BRANCH] fix(effort): run wire mapper on unpinned/unconfigured effort values Sep 22, 2026
@github-actions

github-actions Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • author re-attestation is required for the current head.

What to do

  • Change the first managed item to Required local validation passed; commands, results, and any full-suite exception are documented., clear all four boxes and save. Wait for the bot to acknowledge the cleared checklist before validating and ticking the boxes again.
  • Only a new body edit by the PR author after this notice can advance the checkpoint. If edits share a checkpoint timestamp, make another body edit and save later.

Review readiness checklist

  • ⬜ Required local validation passed; commands, results, and any full-suite exception are documented.
  • ⬜ I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge).
  • ⬜ I resolved all correct Codex and CodeRabbit findings.
  • ⬜ My PR is ready for review.

0/4 boxes ticked.

Current head: eb623d49e874533ec953b6e0b1654c867a12b503. Existing PR text and checkbox marks were preserved.

Hygiene

Deterministic PR hygiene checks passed.

@github-actions
github-actions Bot marked this pull request as draft September 22, 2026 11:57
@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 58 / 80

이 PR은 생각 깊이(effort) 값을 위쪽 서버로 보내기 전에 항상 정리하게 고칩니다. 예전에는 핀(고정값)이나 상한(cap)이 바뀌었을 때만, 또는 제공자에 사다리(허용 목록)가 있을 때만 mapReasoningEffort를 돌렸습니다. 그래서 DeepSeek처럼 엄격한 게이트웨이 앞에서는 minimal·ultra 같은 호출자 맞춤 표기가 그대로 나가 400 Invalid option이 났습니다. 고친 뒤에는 값이 있으면 사다리가 없어도 매퍼를 돌립니다. minimallow, ultramax(그다음 wireMap 별칭)로 접고, 모르는 값은 필드를 빼서 위쪽 기본값에 맡깁니다. none(생각 끄기 지시), enabled/disabled/adaptive(제공자 전용 토글), 압축(compaction) 턴은 예전처럼 손대지 않습니다. 손댄 곳은 src/server/chat-native.ts, src/adapters/openai-responses/reasoning.ts, 그리고 통과 기대를 새 동작에 맞춘 tests/codex-integration/model-pinned-effort.test.ts 세 곳입니다. 제목에도 [WRONG BRANCH]가 붙어 있고, base는 main입니다. draft이고 readiness 체크리스트는 아직 비어 있습니다.

라인 - base main — 선호 base는 dev입니다. enforce-target이 FAILURE이고 제목도 [WRONG BRANCH]입니다. 리뷰·머지 전에 dev로 다시 겨냥해야 합니다.
라인 - src/server/chat-native.ts / src/adapters/openai-responses/reasoning.tsnone·enabled·disabled·adaptive 예외 목록이 두 파일에 똑같이 적혀 있습니다. 나중에 표기가 하나 더 생기면 한쪽만 고칠 위험이 있습니다. 작은 공유 헬퍼(또는 한곳 상수)로 모으는 편이 낫습니다.
라인 - mapRoutedResponsesReasoningEffort 미설정 분기 — 모르는 effort는 필드를 지웁니다(400 대신 위쪽 기본). DeepSeek 게이트웨이에는 맞고, “잘못된 값은 거절”을 원하는 제공자와는 다를 수 있습니다. 이번 버그 범위에서는 합리적입니다.
라인 - draft / readiness 체크리스트 — CI·dev 재기반·Codex/CodeRabbit 처리·ready 표시가 아직입니다. 코드 방향과 별개로 게이트를 통과해야 합니다.

메인테이너의 판단이 필요한 지점

모르는 effort를 조용히 지울지, 아니면 일부 제공자에서는 400을 그대로 둘지. 그리고 예외 목록을 공유 상수로 뺄지, 이번엔 그대로 두고 나중에 할지. base를 dev로 바꾼 뒤 이 PR만 살릴지 확인.

너의 추천

버그 원인과 고침 방향은 맞습니다. 테스트도 예전 “그대로 통과” 기대를 새 매핑에 맞게 고쳤습니다. dev로 base를 바꾸고 CI·체크리스트를 끝낸 뒤 머지하는 쪽을 추천합니다. 예외 문자열 네 개는 가능하면 한곳으로 모으세요. types.ts/config.ts 쪼개기·preview deploy는 이번 범위와 무관합니다.

이 댓글은 grok-bot이 작성했습니다

@lidge-jun

Copy link
Copy Markdown
Owner

Reviewed for the 260923 bundle round (lane F2, #5674) and not carried. On current dev this change would reverse two behaviours that existing tests lock in for unpinned and unconfigured effort values, and the 400 it targets was not reproduced. This PR also targets main; changes go to dev. If you can share a request that still fails on current dev, a fix scoped to that case can be reviewed against the existing tests.

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Maintainer triage: priority: P2 — effort wire mapper skipped on unpinned values.

Criteria (P2): Medium: provider/client-specific bug with a workaround, bounded enhancement tied to a tracked issue, perf, or CI reliability.

Rebased onto current dev: branch rebase/pr-5539 @ e97a7c9da (compare). Your fork branch could not be updated directly; you can adopt it with git fetch https://github.com/lidge-jun/opencodex.git rebase/pr-5539 && git reset --hard FETCH_HEAD && git push --force-with-lease. CI was intentionally not run.

Related issues:

Related / overlapping PRs:

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working priority: P2 Medium: provider/client-specific bug with a workaround, bounded enhancement tied to a tracked issue,

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants