Skip to content

fix: harden relay reliability and billing integrity - #6201

Closed
superman2003 wants to merge 2 commits into
QuantumNous:mainfrom
superman2003:fix/full-bug-audit-20260714
Closed

fix: harden relay reliability and billing integrity#6201
superman2003 wants to merge 2 commits into
QuantumNous:mainfrom
superman2003:fix/full-bug-audit-20260714

Conversation

@superman2003

@superman2003 superman2003 commented Jul 14, 2026

Copy link
Copy Markdown

Note

Supersedes #6200, which was closed before the review-fix commit could be pushed. All 14 inline findings, 2 outside-diff findings, 4 nitpicks, and the Docstring Coverage warning are addressed in 518d4d6.

⚠️ 提交说明 / PR Notice > [!IMPORTANT] > > - AI-assisted disclosure: implementation, review, tests, and this PR draft were prepared with AI assistance. The changes were manually organized into the repository's PR template and should receive maintainer review before merge. ## 📝 变更描述 / Description This PR fixes the audited failure paths behind intermittent relay 503 upstream busy errors and several accounting/security inconsistencies: - Harden relay failover with bounded exponential jitter, Retry-After, failed-channel exclusion, affinity invalidation, multi-key rotation, original upstream status preservation, and an optional first-byte timeout. - Treat empty streams, premature EOF, malformed chunks, and HTTP 200 business-error payloads as retryable upstream failures before real model output; after output begins, emit protocol-correct OpenAI Chat, Responses, Claude, or Gemini error events without mixing channels. - Close response bodies on all read/error paths and replace unimplemented adaptor panics with explicit errors. - Keep scoped subscription billing inside the purchase-time group snapshot, make subscription refunds atomic/idempotent, validate task usage, snapshot async-video billing ratios, and prevent duplicate Suno refunds/used-quota drift. - Enforce external-login 2FA and disabled-user gates, validate token groups, redact channel details from user logs, and filter Pricing groups by actual availability. - Preserve xAI cached-token usage, Advanced Custom include_usage, Codex SSE detection, Ali fractional duration, and Cloudflare embedding payloads. ## 🚀 变更类型 / Type of change - [x] 🐛 Bug 修复 (Bug fix) - [x] ✨ 新功能 (New feature: optional STREAMING_FIRST_BYTE_TIMEOUT) - [ ] ⚡ 性能优化 / 重构 (Refactor) - [x] 📝 文档更新 (Documentation) ## 🔗 关联任务 / Related Issue Closes #2989 Closes #3192 Closes #4139 Closes #5049 Closes #6095 Closes #6141 Closes #4541 Closes #6021 Closes #6144 Closes #6149 Closes #6075 Closes #6166 Closes #6172 Closes #5200 Closes #5556 Closes #5816 Closes #4211 Closes #6125 Closes #6175 Closes #5139 Known partial overlap with open single-issue PRs: #4060, #6145, #6082, #6174, #6173, #6090, #6089, #5817, #6091, #4323, and #6176. This PR keeps the fixes together because retry selection, stream write boundaries, and billing funding-source invariants share cross-module tests and behavior. ## ✅ 提交前检查项 / Checklist - [ ] 人工确认: AI-assisted draft; awaiting human/maintainer confirmation. - [ ] 非重复提交: Partial overlaps are explicitly listed above. - [x] Bug fix 说明: Corresponding Issues are linked above. - [x] 变更理解: The changed retry, protocol, funding, and authorization paths were reviewed together with their tests. - [x] 范围聚焦: Changes are limited to the audited relay, billing, task, authorization, and compatibility defects. - [x] 本地验证: Focused package tests, repeated concurrency/timeout tests, and go vet pass locally. - [x] 安全合规: Staged diff was checked for credentials and sensitive channel data exposure. ## 📸 运行证明 / Proof of Work Passed: text go vet -p 1 ./controller ./model ./service ./relay/... ./dto ./middleware ./setting/... ./types/... go test -p 1 ./controller ./model ./service ./relay/... ./dto ./middleware ./setting/... ./types/... -count=1 go test -p 1 ./relay/channel -run 'TestDoRequest.*FirstByte' -count=10 go test -p 1 ./service -run 'TestUpdateSunoTasksConcurrentFailureRefundsOnce|TestSettleTaskBilling_OldSnapshotKeepsLegacyCompletionRatio|TestRecalculateTaskQuotaByTokens_NormalizesInvalidUsage' -count=50 go test -p 1 ./relay/channel/baidu -run TestBaiduStreamHandlerTreatsIsEndFollowedByEOFAsDone -count=100 git diff --cached --check go test -p 1 ./... -count=1 passed every buildable package; only the root package setup failed because this checkout does not contain the generated web/classic/dist required by the existing go:embed. Race tests could not start because the local Go toolchain has CGO_ENABLED=0. Known architectural follow-up: async task terminal-state CAS and all billing side effects are not yet one durable outbox transaction. This PR adds idempotency/CAS protections for the audited duplicate-refund paths but intentionally avoids a database migration for a generalized settlement ledger.

Summary by CodeRabbit

  • New Features

    • Added an independent first-byte timeout for streaming responses, configurable in seconds and disabled by default.
    • Improved streaming error reporting, completion handling, usage estimates, and retry behavior across providers.
    • Added stronger subscription, quota, token, and task settlement safeguards.
    • OAuth, Telegram, WeChat, and password login flows now consistently enforce account status and two-factor authentication.
    • Restricted pricing, tokens, and logs to information available to authorized accounts.
  • Bug Fixes

    • Improved response cleanup, upstream status handling, malformed-response protection, and unsupported-route error behavior.
  • Documentation

    • Documented the new streaming timeout setting in configuration examples and multilingual READMEs.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 047dbe93-a8a6-4212-9ab8-a20f3c3835a7

📥 Commits

Reviewing files that changed from the base of the PR and between 9a2d660 and 518d4d6.

📒 Files selected for processing (115)
  • .env.example
  • README.en.md
  • README.fr.md
  • README.ja.md
  • README.md
  • README.zh_CN.md
  • README.zh_TW.md
  • common/constants.go
  • common/init.go
  • constant/context_key.go
  • controller/channel-billing.go
  • controller/channel.go
  • controller/login_2fa_gate_test.go
  • controller/oauth.go
  • controller/pricing.go
  • controller/pricing_test.go
  • controller/relay.go
  • controller/relay_retry.go
  • controller/relay_retry_test.go
  • controller/telegram.go
  • controller/token.go
  • controller/token_test.go
  • controller/user.go
  • controller/wechat.go
  • docker-compose.yml
  • dto/openai_response.go
  • dto/openai_response_test.go
  • dto/task.go
  • model/ability.go
  • model/channel_cache.go
  • model/channel_retry_test.go
  • model/log.go
  • model/log_user_format_test.go
  • model/subscription.go
  • model/subscription_safety_test.go
  • model/task.go
  • relay/channel/advancedcustom/adaptor.go
  • relay/channel/advancedcustom/adaptor_test.go
  • relay/channel/ali/image.go
  • relay/channel/ali/rerank.go
  • relay/channel/api_request.go
  • relay/channel/api_request_first_byte_timeout_test.go
  • relay/channel/baidu/adaptor.go
  • relay/channel/baidu/relay-baidu.go
  • relay/channel/baidu/relay_baidu_test.go
  • relay/channel/claude/relay-claude.go
  • relay/channel/claude/relay_claude_test.go
  • relay/channel/cloudflare/adaptor.go
  • relay/channel/cloudflare/relay_cloudflare.go
  • relay/channel/cloudflare/relay_cloudflare_test.go
  • relay/channel/cohere/adaptor.go
  • relay/channel/cohere/relay-cohere.go
  • relay/channel/cohere/relay_cohere_test.go
  • relay/channel/coze/adaptor.go
  • relay/channel/coze/adaptor_test.go
  • relay/channel/coze/relay-coze.go
  • relay/channel/dify/adaptor.go
  • relay/channel/dify/relay-dify.go
  • relay/channel/dify/relay_dify_test.go
  • relay/channel/gemini/relay-gemini.go
  • relay/channel/jimeng/image.go
  • relay/channel/jina/adaptor.go
  • relay/channel/minimax/image.go
  • relay/channel/mistral/adaptor.go
  • relay/channel/mokaai/adaptor.go
  • relay/channel/ollama/stream.go
  • relay/channel/ollama/stream_test.go
  • relay/channel/openai/chat_via_responses.go
  • relay/channel/openai/chat_via_responses_test.go
  • relay/channel/openai/error.go
  • relay/channel/openai/error_test.go
  • relay/channel/openai/image_stream_test.go
  • relay/channel/openai/relay-openai.go
  • relay/channel/openai/relay_image.go
  • relay/channel/openai/relay_responses.go
  • relay/channel/openai/relay_responses_compact.go
  • relay/channel/openai/responses_via_chat.go
  • relay/channel/palm/adaptor.go
  • relay/channel/palm/relay-palm.go
  • relay/channel/palm/relay-palm_test.go
  • relay/channel/task/ali/adaptor.go
  • relay/channel/task/ali/adaptor_test.go
  • relay/channel/tencent/adaptor.go
  • relay/channel/tencent/relay-tencent.go
  • relay/channel/xai/text.go
  • relay/channel/xai/text_test.go
  • relay/channel/xunfei/adaptor.go
  • relay/channel/zhipu/adaptor.go
  • relay/channel/zhipu/relay-zhipu.go
  • relay/channel/zhipu/relay-zhipu_test.go
  • relay/channel/zhipu_4v/image.go
  • relay/chat_completions_via_responses.go
  • relay/chat_completions_via_responses_test.go
  • relay/helper/common.go
  • relay/helper/common_output_test.go
  • relay/helper/stream_result.go
  • relay/helper/stream_scanner.go
  • relay/helper/stream_scanner_test.go
  • relay/relay_adaptor_test.go
  • relay/relay_task.go
  • service/billing_session.go
  • service/billing_session_test.go
  • service/channel.go
  • service/channel_affinity_template_test.go
  • service/channel_affinity_usage_cache_test.go
  • service/channel_select.go
  • service/error.go
  • service/error_test.go
  • service/funding_source.go
  • service/task_billing.go
  • service/task_billing_test.go
  • service/task_polling.go
  • service/task_polling_test.go
  • setting/operation_setting/channel_affinity_setting.go
  • types/error.go

Walkthrough

Changes

The PR adds an independent streaming first-byte timeout, improves SSE error and completion handling, strengthens retry/channel selection, applies shared 2FA gates, enforces usable pricing and token groups, introduces atomic subscription-aware task settlement, and improves provider response cleanup and error normalization.

Core relay and streaming behavior

Layer / File(s) Summary
Streaming timeout and protocol handling
relay/channel/api_request.go, relay/helper/*, relay/chat_completions_via_responses.go
Streaming requests can enforce a first-byte deadline while preserving subsequent body streaming; SSE detection, keepalive tracking, terminal-state validation, and protocol-specific error rendering are expanded.
Provider stream robustness
relay/channel/*/relay-*.go, relay/channel/*/*_test.go
Provider handlers now promote business errors, handle partial or malformed streams, backfill usage, preserve terminal states, and close upstream bodies on early exits.
Retry and upstream error metadata
controller/relay.go, controller/relay_retry.go, service/error.go, types/error.go
Retries track excluded channels, honor bounded backoff and Retry-After, stop after real output, and preserve upstream status metadata.
Authentication and access controls
controller/user.go, controller/oauth.go, controller/telegram.go, controller/wechat.go, controller/token.go, controller/pricing.go
OAuth and external login flows use the shared 2FA gate; disabled users cannot complete pending login; token groups and pricing metadata are restricted to usable groups.
Subscription-aware billing settlement
model/subscription.go, model/task.go, service/task_billing.go, service/task_polling.go
Subscription upgrade groups are carried through pre-consumption and billing, while task quota changes use atomic settlement transactions and polling propagates settlement failures.
Adaptor and payload compatibility
relay/channel/*/adaptor.go, dto/openai_response.go, relay/channel/task/ali/adaptor.go
Unsupported Claude conversions return errors instead of panicking, Responses stream errors accept top-level fields, advanced custom streams request usage, and Ali duration parsing accepts validated decimals.
Configuration and documentation
.env.example, README*.md, docker-compose.yml, setting/operation_setting/*
The first-byte timeout and affinity retry defaults are documented and exposed through configuration examples.
Resource cleanup and supporting tests
controller/channel*.go, controller/*_test.go, relay/*_test.go, service/*_test.go, model/*_test.go
Response-body closure, login gates, group filtering, retry behavior, quota settlement, stream handling, and provider-specific contracts receive test coverage.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: quentinhsu, seefs001

Poem

I twitch my nose as streams begin,
First bytes arrive; the pings stay thin.
Errors wear a clearer face,
Quotas settle in one place.
Channels hop when paths go wrong—
A rabbit cheers the flowing song.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@superman2003

This comment was marked as low quality.

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