Repository navigation
fix: pass through valid max_tokens-truncated responses instead of fake 502 (#3572) - #3595
Conversation
There was a problem hiding this comment.
Code Review
This pull request addresses an issue where valid, truncated upstream responses (such as those with a max_tokens or tool_use stop reason) were incorrectly flagged as empty content and rewritten into synthetic 502 errors. The isEmptyContentResponse utility in open-sse/services/errorClassifier.ts was updated to recognize legitimate terminal stop signals for both Claude and OpenAI providers, and comprehensive unit tests were added under tests/unit/ to verify this behavior. No review comments were provided, so there is no feedback to address.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge This PR correctly addresses the issue where valid The fix preserves the original fake-success guard for genuinely empty responses while allowing valid truncated/tool-use responses to pass through. The test coverage (8 cases) is comprehensive and validates all intended behaviors. Files Reviewed (3 files)
Reviewed by laguna-m.1-20260312:free · 2,170,947 tokens |
…e 502 (cheaper-inference#3572) (cheaper-inference#3595) (cherry picked from commit 1bc8588)
* chore(release): open v3.8.21 development cycle * fix: pass through valid max_tokens-truncated responses instead of fake 502 (#3572) (#3595) * fix: /v1/completions returns legacy text-completion format, not chat (#3571) (#3596) * fix: z.ai/GLM coding plan no longer shows Monthly 0% when no monthly cap (#3580) (#3597) * docs: mark DISCOVERY_TOOL_DESIGN endpoints as Phase-2 not-yet-implemented (#3498) (#3599) * fix(agent-bridge): add validate-only upstream-ca/test route (#3488) (#3600) * fix(gamification): add level/badges/badges-earned profile routes (#3484) * security(oauth): migrate 5 public client_ids to resolvePublicCred (#3493) * fix(mcp): ship MCP server source closure in npm files + coverage gate (#3578) * fix: add reasoning token buffer for combo routing (fixes #3587) (#3588) Integrated into release/v3.8.21 * Refactor: Extract chatCore phases into modular files (#3598) Integrated into release/v3.8.21 — chatCore phase modularization. Adjusted: re-derive idempotencyKey for the save path after the check moved into the module (co-authored). Thanks @oyi77! * docs(changelog): credit #3598 (chatCore modularization) + #3588 (combo reasoning buffer) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(api): implement GET /api/guardrails + POST /api/guardrails/test, drop shadow/guardrails doc-fiction (#3496) (#3602) Integrated into release/v3.8.21 — implements GET /api/guardrails + POST /api/guardrails/test, removes shadow/guardrails doc-fiction. TDD-validated (5/5) + check-docs-symbols/typecheck/eslint green. * fix(gemini): isolate textual reasoning wrappers (#3605) Split-out PR C from #3584. Isolates textual reasoning wrappers (<think>/<thinking>/<thought>/<internal_thought>, including malformed/open tags) into reasoning_content across both the non-streaming sanitizer and the Gemini streaming translator, with split-chunk buffering. Additive to the existing textual tool-call pipeline; does not touch the #3569 native functionResponse path. Integrated into release/v3.8.21. Thanks @dhaern! * fix(antigravity): normalize Gemini 3.5 Flash tier IDs (#3603) Split-out PR A from #3584. Normalizes the Antigravity/agy Gemini 3.5 Flash tier IDs to clean public names (gemini-3.5-flash-low/medium/high), maps them to the live upstream IDs at the executor boundary, and removes Antigravity from the global model resolver so the executor owns wire normalization. Maintainer follow-up: kept gemini-3.5-flash-preview as a hidden backward-compat alias routing to the High tier (so saved combos/configs keep working). Live-validated the tier set via the agy CLI catalog. Integrated into release/v3.8.21. Thanks @dhaern! * fix(agent-bridge): surface real MITM startup-failure cause, not always port 443 (#3606) (#3608) Integrated into release/v3.8.21 (#3606) * fix(oauth): surface real Kiro import-token failure cause, not a bare 500 (#3589) (#3609) Integrated into release/v3.8.21 (#3589) * docs(opencode-provider): soft-deprecate in favor of @omniroute/opencode-plugin (#3419) (#3613) Integrated into release/v3.8.21 (#3419) * fix(usage): normalize Antigravity and agy provider quotas (#3604) Split-out PR B from #3584. Normalizes Antigravity/agy provider quotas: prefers retrieveUserQuota for live consumption, falls back to fetchAvailableModels and local usage_history, sanitizes cached Provider Limits so retired upstream IDs are not re-exposed, and schedules a deduplicated post-usage refresh. Maintainer follow-up: decoupled the post-usage refresh via a lightweight usageEvents bus (usageHistory no longer dynamic-imports providerLimits) so it does not pull the executors/translator graph into the typecheck-core surface — typecheck:core stays at 0. Integrated into release/v3.8.21. Thanks @dhaern! * feat(cli): add autostart on/off/toggle shorthand for headless serve mode (#3331) (#3614) Integrated into release/v3.8.21 (#3331) * docs(changelog): credit #3603 (Flash tier IDs) + #3604 (provider quotas) + #3605 (reasoning wrappers) Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(review): resolve findings from /review-reviews battery (v3.8.21 hardening) (#3618) Pre-release hardening from the /review-reviews battery — 15 findings resolved (L1-L13,L15) + L14 live-verified WONTFIX, convergence re-review clean. lint/typecheck:core/test:vitest(146)/build green; zero new test:unit failures vs baseline 797de43. * chore(release): v3.8.21 CHANGELOG + i18n + env-doc sync --------- Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Raxxoor <manker_lol@hotmail.com>
* chore(release): open v3.8.21 development cycle * fix: pass through valid max_tokens-truncated responses instead of fake 502 (cheaper-inference#3572) (cheaper-inference#3595) * fix: /v1/completions returns legacy text-completion format, not chat (cheaper-inference#3571) (cheaper-inference#3596) * fix: z.ai/GLM coding plan no longer shows Monthly 0% when no monthly cap (cheaper-inference#3580) (cheaper-inference#3597) * docs: mark DISCOVERY_TOOL_DESIGN endpoints as Phase-2 not-yet-implemented (cheaper-inference#3498) (cheaper-inference#3599) * fix(agent-bridge): add validate-only upstream-ca/test route (cheaper-inference#3488) (cheaper-inference#3600) * fix(gamification): add level/badges/badges-earned profile routes (cheaper-inference#3484) * security(oauth): migrate 5 public client_ids to resolvePublicCred (cheaper-inference#3493) * fix(mcp): ship MCP server source closure in npm files + coverage gate (cheaper-inference#3578) * fix: add reasoning token buffer for combo routing (fixes cheaper-inference#3587) (cheaper-inference#3588) Integrated into release/v3.8.21 * Refactor: Extract chatCore phases into modular files (cheaper-inference#3598) Integrated into release/v3.8.21 — chatCore phase modularization. Adjusted: re-derive idempotencyKey for the save path after the check moved into the module (co-authored). Thanks @oyi77! * docs(changelog): credit cheaper-inference#3598 (chatCore modularization) + cheaper-inference#3588 (combo reasoning buffer) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(api): implement GET /api/guardrails + POST /api/guardrails/test, drop shadow/guardrails doc-fiction (cheaper-inference#3496) (cheaper-inference#3602) Integrated into release/v3.8.21 — implements GET /api/guardrails + POST /api/guardrails/test, removes shadow/guardrails doc-fiction. TDD-validated (5/5) + check-docs-symbols/typecheck/eslint green. * fix(gemini): isolate textual reasoning wrappers (cheaper-inference#3605) Split-out PR C from cheaper-inference#3584. Isolates textual reasoning wrappers (<think>/<thinking>/<thought>/<internal_thought>, including malformed/open tags) into reasoning_content across both the non-streaming sanitizer and the Gemini streaming translator, with split-chunk buffering. Additive to the existing textual tool-call pipeline; does not touch the cheaper-inference#3569 native functionResponse path. Integrated into release/v3.8.21. Thanks @dhaern! * fix(antigravity): normalize Gemini 3.5 Flash tier IDs (cheaper-inference#3603) Split-out PR A from cheaper-inference#3584. Normalizes the Antigravity/agy Gemini 3.5 Flash tier IDs to clean public names (gemini-3.5-flash-low/medium/high), maps them to the live upstream IDs at the executor boundary, and removes Antigravity from the global model resolver so the executor owns wire normalization. Maintainer follow-up: kept gemini-3.5-flash-preview as a hidden backward-compat alias routing to the High tier (so saved combos/configs keep working). Live-validated the tier set via the agy CLI catalog. Integrated into release/v3.8.21. Thanks @dhaern! * fix(agent-bridge): surface real MITM startup-failure cause, not always port 443 (cheaper-inference#3606) (cheaper-inference#3608) Integrated into release/v3.8.21 (cheaper-inference#3606) * fix(oauth): surface real Kiro import-token failure cause, not a bare 500 (cheaper-inference#3589) (cheaper-inference#3609) Integrated into release/v3.8.21 (cheaper-inference#3589) * docs(opencode-provider): soft-deprecate in favor of @omniroute/opencode-plugin (cheaper-inference#3419) (cheaper-inference#3613) Integrated into release/v3.8.21 (cheaper-inference#3419) * fix(usage): normalize Antigravity and agy provider quotas (cheaper-inference#3604) Split-out PR B from cheaper-inference#3584. Normalizes Antigravity/agy provider quotas: prefers retrieveUserQuota for live consumption, falls back to fetchAvailableModels and local usage_history, sanitizes cached Provider Limits so retired upstream IDs are not re-exposed, and schedules a deduplicated post-usage refresh. Maintainer follow-up: decoupled the post-usage refresh via a lightweight usageEvents bus (usageHistory no longer dynamic-imports providerLimits) so it does not pull the executors/translator graph into the typecheck-core surface — typecheck:core stays at 0. Integrated into release/v3.8.21. Thanks @dhaern! * feat(cli): add autostart on/off/toggle shorthand for headless serve mode (cheaper-inference#3331) (cheaper-inference#3614) Integrated into release/v3.8.21 (cheaper-inference#3331) * docs(changelog): credit cheaper-inference#3603 (Flash tier IDs) + cheaper-inference#3604 (provider quotas) + cheaper-inference#3605 (reasoning wrappers) Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(review): resolve findings from /review-reviews battery (v3.8.21 hardening) (cheaper-inference#3618) Pre-release hardening from the /review-reviews battery — 15 findings resolved (L1-L13,L15) + L14 live-verified WONTFIX, convergence re-review clean. lint/typecheck:core/test:vitest(146)/build green; zero new test:unit failures vs baseline 6d24708. * chore(release): v3.8.21 CHANGELOG + i18n + env-doc sync --------- Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Raxxoor <manker_lol@hotmail.com>
* chore(release): open v3.8.21 development cycle * fix: pass through valid max_tokens-truncated responses instead of fake 502 (cheaper-inference#3572) (cheaper-inference#3595) * fix: /v1/completions returns legacy text-completion format, not chat (cheaper-inference#3571) (cheaper-inference#3596) * fix: z.ai/GLM coding plan no longer shows Monthly 0% when no monthly cap (cheaper-inference#3580) (cheaper-inference#3597) * docs: mark DISCOVERY_TOOL_DESIGN endpoints as Phase-2 not-yet-implemented (cheaper-inference#3498) (cheaper-inference#3599) * fix(agent-bridge): add validate-only upstream-ca/test route (cheaper-inference#3488) (cheaper-inference#3600) * fix(gamification): add level/badges/badges-earned profile routes (cheaper-inference#3484) * security(oauth): migrate 5 public client_ids to resolvePublicCred (cheaper-inference#3493) * fix(mcp): ship MCP server source closure in npm files + coverage gate (cheaper-inference#3578) * fix: add reasoning token buffer for combo routing (fixes cheaper-inference#3587) (cheaper-inference#3588) Integrated into release/v3.8.21 * Refactor: Extract chatCore phases into modular files (cheaper-inference#3598) Integrated into release/v3.8.21 — chatCore phase modularization. Adjusted: re-derive idempotencyKey for the save path after the check moved into the module (co-authored). Thanks @oyi77! * docs(changelog): credit cheaper-inference#3598 (chatCore modularization) + cheaper-inference#3588 (combo reasoning buffer) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(api): implement GET /api/guardrails + POST /api/guardrails/test, drop shadow/guardrails doc-fiction (cheaper-inference#3496) (cheaper-inference#3602) Integrated into release/v3.8.21 — implements GET /api/guardrails + POST /api/guardrails/test, removes shadow/guardrails doc-fiction. TDD-validated (5/5) + check-docs-symbols/typecheck/eslint green. * fix(gemini): isolate textual reasoning wrappers (cheaper-inference#3605) Split-out PR C from cheaper-inference#3584. Isolates textual reasoning wrappers (<think>/<thinking>/<thought>/<internal_thought>, including malformed/open tags) into reasoning_content across both the non-streaming sanitizer and the Gemini streaming translator, with split-chunk buffering. Additive to the existing textual tool-call pipeline; does not touch the cheaper-inference#3569 native functionResponse path. Integrated into release/v3.8.21. Thanks @dhaern! * fix(antigravity): normalize Gemini 3.5 Flash tier IDs (cheaper-inference#3603) Split-out PR A from cheaper-inference#3584. Normalizes the Antigravity/agy Gemini 3.5 Flash tier IDs to clean public names (gemini-3.5-flash-low/medium/high), maps them to the live upstream IDs at the executor boundary, and removes Antigravity from the global model resolver so the executor owns wire normalization. Maintainer follow-up: kept gemini-3.5-flash-preview as a hidden backward-compat alias routing to the High tier (so saved combos/configs keep working). Live-validated the tier set via the agy CLI catalog. Integrated into release/v3.8.21. Thanks @dhaern! * fix(agent-bridge): surface real MITM startup-failure cause, not always port 443 (cheaper-inference#3606) (cheaper-inference#3608) Integrated into release/v3.8.21 (cheaper-inference#3606) * fix(oauth): surface real Kiro import-token failure cause, not a bare 500 (cheaper-inference#3589) (cheaper-inference#3609) Integrated into release/v3.8.21 (cheaper-inference#3589) * docs(opencode-provider): soft-deprecate in favor of @omniroute/opencode-plugin (cheaper-inference#3419) (cheaper-inference#3613) Integrated into release/v3.8.21 (cheaper-inference#3419) * fix(usage): normalize Antigravity and agy provider quotas (cheaper-inference#3604) Split-out PR B from cheaper-inference#3584. Normalizes Antigravity/agy provider quotas: prefers retrieveUserQuota for live consumption, falls back to fetchAvailableModels and local usage_history, sanitizes cached Provider Limits so retired upstream IDs are not re-exposed, and schedules a deduplicated post-usage refresh. Maintainer follow-up: decoupled the post-usage refresh via a lightweight usageEvents bus (usageHistory no longer dynamic-imports providerLimits) so it does not pull the executors/translator graph into the typecheck-core surface — typecheck:core stays at 0. Integrated into release/v3.8.21. Thanks @dhaern! * feat(cli): add autostart on/off/toggle shorthand for headless serve mode (cheaper-inference#3331) (cheaper-inference#3614) Integrated into release/v3.8.21 (cheaper-inference#3331) * docs(changelog): credit cheaper-inference#3603 (Flash tier IDs) + cheaper-inference#3604 (provider quotas) + cheaper-inference#3605 (reasoning wrappers) Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(review): resolve findings from /review-reviews battery (v3.8.21 hardening) (cheaper-inference#3618) Pre-release hardening from the /review-reviews battery — 15 findings resolved (L1-L13,L15) + L14 live-verified WONTFIX, convergence re-review clean. lint/typecheck:core/test:vitest(146)/build green; zero new test:unit failures vs baseline 797de43. * chore(release): v3.8.21 CHANGELOG + i18n + env-doc sync --------- Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Raxxoor <manker_lol@hotmail.com>
* chore(release): open v3.8.21 development cycle * fix: pass through valid max_tokens-truncated responses instead of fake 502 (cheaper-inference#3572) (cheaper-inference#3595) * fix: /v1/completions returns legacy text-completion format, not chat (cheaper-inference#3571) (cheaper-inference#3596) * fix: z.ai/GLM coding plan no longer shows Monthly 0% when no monthly cap (cheaper-inference#3580) (cheaper-inference#3597) * docs: mark DISCOVERY_TOOL_DESIGN endpoints as Phase-2 not-yet-implemented (cheaper-inference#3498) (cheaper-inference#3599) * fix(agent-bridge): add validate-only upstream-ca/test route (cheaper-inference#3488) (cheaper-inference#3600) * fix(gamification): add level/badges/badges-earned profile routes (cheaper-inference#3484) * security(oauth): migrate 5 public client_ids to resolvePublicCred (cheaper-inference#3493) * fix(mcp): ship MCP server source closure in npm files + coverage gate (cheaper-inference#3578) * fix: add reasoning token buffer for combo routing (fixes cheaper-inference#3587) (cheaper-inference#3588) Integrated into release/v3.8.21 * Refactor: Extract chatCore phases into modular files (cheaper-inference#3598) Integrated into release/v3.8.21 — chatCore phase modularization. Adjusted: re-derive idempotencyKey for the save path after the check moved into the module (co-authored). Thanks @oyi77! * docs(changelog): credit cheaper-inference#3598 (chatCore modularization) + cheaper-inference#3588 (combo reasoning buffer) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(api): implement GET /api/guardrails + POST /api/guardrails/test, drop shadow/guardrails doc-fiction (cheaper-inference#3496) (cheaper-inference#3602) Integrated into release/v3.8.21 — implements GET /api/guardrails + POST /api/guardrails/test, removes shadow/guardrails doc-fiction. TDD-validated (5/5) + check-docs-symbols/typecheck/eslint green. * fix(gemini): isolate textual reasoning wrappers (cheaper-inference#3605) Split-out PR C from cheaper-inference#3584. Isolates textual reasoning wrappers (<think>/<thinking>/<thought>/<internal_thought>, including malformed/open tags) into reasoning_content across both the non-streaming sanitizer and the Gemini streaming translator, with split-chunk buffering. Additive to the existing textual tool-call pipeline; does not touch the cheaper-inference#3569 native functionResponse path. Integrated into release/v3.8.21. Thanks @dhaern! * fix(antigravity): normalize Gemini 3.5 Flash tier IDs (cheaper-inference#3603) Split-out PR A from cheaper-inference#3584. Normalizes the Antigravity/agy Gemini 3.5 Flash tier IDs to clean public names (gemini-3.5-flash-low/medium/high), maps them to the live upstream IDs at the executor boundary, and removes Antigravity from the global model resolver so the executor owns wire normalization. Maintainer follow-up: kept gemini-3.5-flash-preview as a hidden backward-compat alias routing to the High tier (so saved combos/configs keep working). Live-validated the tier set via the agy CLI catalog. Integrated into release/v3.8.21. Thanks @dhaern! * fix(agent-bridge): surface real MITM startup-failure cause, not always port 443 (cheaper-inference#3606) (cheaper-inference#3608) Integrated into release/v3.8.21 (cheaper-inference#3606) * fix(oauth): surface real Kiro import-token failure cause, not a bare 500 (cheaper-inference#3589) (cheaper-inference#3609) Integrated into release/v3.8.21 (cheaper-inference#3589) * docs(opencode-provider): soft-deprecate in favor of @omniroute/opencode-plugin (cheaper-inference#3419) (cheaper-inference#3613) Integrated into release/v3.8.21 (cheaper-inference#3419) * fix(usage): normalize Antigravity and agy provider quotas (cheaper-inference#3604) Split-out PR B from cheaper-inference#3584. Normalizes Antigravity/agy provider quotas: prefers retrieveUserQuota for live consumption, falls back to fetchAvailableModels and local usage_history, sanitizes cached Provider Limits so retired upstream IDs are not re-exposed, and schedules a deduplicated post-usage refresh. Maintainer follow-up: decoupled the post-usage refresh via a lightweight usageEvents bus (usageHistory no longer dynamic-imports providerLimits) so it does not pull the executors/translator graph into the typecheck-core surface — typecheck:core stays at 0. Integrated into release/v3.8.21. Thanks @dhaern! * feat(cli): add autostart on/off/toggle shorthand for headless serve mode (cheaper-inference#3331) (cheaper-inference#3614) Integrated into release/v3.8.21 (cheaper-inference#3331) * docs(changelog): credit cheaper-inference#3603 (Flash tier IDs) + cheaper-inference#3604 (provider quotas) + cheaper-inference#3605 (reasoning wrappers) Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com> * fix(review): resolve findings from /review-reviews battery (v3.8.21 hardening) (cheaper-inference#3618) Pre-release hardening from the /review-reviews battery — 15 findings resolved (L1-L13,L15) + L14 live-verified WONTFIX, convergence re-review clean. lint/typecheck:core/test:vitest(146)/build green; zero new test:unit failures vs baseline 408d91a2c. * chore(release): v3.8.21 CHANGELOG + i18n + env-doc sync --------- Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: Paijo <14921983+oyi77@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Raxxoor <manker_lol@hotmail.com>
Closes #3572
Problem
A Claude Code
max_tokens: 1connectivity ping legitimately returns HTTP 200 withstop_reason: "max_tokens"andcontent: [].isEmptyContentResponse()flagged any empty-content payload regardless of stop_reason/finish_reason, so OmniRoute rewrote the valid 200 into a synthetic502 "Provider returned empty content"and triggered a needless family fallback (16 false 502s in one morning on a prod proxy, per the report).Fix (
open-sse/services/errorClassifier.ts)Treat a terminal truncation/tool signal as a legitimate completion even when content is empty:
content[]is only flagged whenstop_reason∉ {max_tokens,tool_use}.finish_reason∈ {length,tool_calls}.Genuinely empty responses (no terminal reason, or
stop/end_turnwith empty content) are still caught — the fake-success guard for web/no-auth providers is preserved.Regression test (Rule #18 — TDD, RED→GREEN)
tests/unit/empty-content-stopreason-3572.test.ts(8 cases): max_tokens/length pass-through, no-stop_reason still flagged, end_turn/stop still flagged. Failed on unfixed code (3 RED), passes after. Existingservices-branch-hardeningempty-content assertions stay green.