Skip to content

fix(observability): classify DeepSeek 'Insufficient Balance' 402 as user-state (Sentry TAURI-RUST-4ZF)#2809

Open
CodeGhost21 wants to merge 1 commit into
tinyhumansai:mainfrom
CodeGhost21:fix/sentry-tauri-rust-4zf-insufficient-balance
Open

fix(observability): classify DeepSeek 'Insufficient Balance' 402 as user-state (Sentry TAURI-RUST-4ZF)#2809
CodeGhost21 wants to merge 1 commit into
tinyhumansai:mainfrom
CodeGhost21:fix/sentry-tauri-rust-4zf-insufficient-balance

Conversation

@CodeGhost21
Copy link
Copy Markdown
Contributor

@CodeGhost21 CodeGhost21 commented May 28, 2026

Summary

Add "insufficient balance" to is_provider_config_rejection_message's PHRASES array (src/openhuman/inference/provider/config_rejection.rs). A user's custom BYO-key DeepSeek provider returns HTTP 402 {"error":{"message":"… Insufficient Balance …"}} when their DeepSeek account balance is exhausted. Pure user-billing state — the remediation is "top up the DeepSeek account", which Sentry has no way to act on.

Targets Sentry TAURI-RUST-4ZF (domain=llm_provider, operation=native_chat, status=402, provider=custom, model=ds/deepseek-v4-flash).

Why this belongs in the existing classifier

This is the same class as the OpenRouter S5 "requires more credits" 402 already in the list — third-party BYO-key provider reporting the user is out of credits. Different upstream, different wire token:

Sentry ID Provider Wire token Status
S5 (existing) OpenRouter requires more credits 402
4ZF (this PR) DeepSeek Insufficient Balance 402

The OpenHuman backend's own balance gate is handled separately by is_budget_exhausted_message (different body shape), so there's no backend false-positive risk: "Insufficient Balance" is a DeepSeek-specific token our backend never emits. The matcher is case-insensitive (body.to_ascii_lowercase()), so capitalisation variants are covered.

Tests

  • detects_insufficient_balance_402_family — verbatim TAURI-RUST-4ZF wire shape (truncated body from issue 5679) + a bare upstream envelope.
  • Existing ignores_transient_and_server_and_unrelated stays green — its negative case "insufficient budget — add credits" does NOT match ("budget""balance"), so the new phrase doesn't over-reach.

Related

PR #2796 (Sentry TAURI-RUST-35 family, "does not support tools") also appends to this PHRASES array, but at a different position (end of list vs. next to the S5 entry) — no merge conflict expected.

Test plan

  • cargo test detects_insufficient_balance_402_family — passes
  • cargo test config_rejection — 7 tests pass, 0 regressions
  • cargo check --manifest-path Cargo.toml --bin openhuman-core — passes
  • cargo fmt --check — clean

Note on push: the pre-push hook failed on pre-existing ESLint warnings in app/src/components/BootCheckGate/BootCheckGate.tsx and RotatingTetrahedronCanvas.tsx (react-hooks/set-state-in-effect) — frontend React files untouched by this Rust-only change. Pushed with --no-verify per the CLAUDE.md guidance for unrelated pre-existing breakage.

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced error detection for DeepSeek API configuration issues to properly recognize insufficient balance errors, enabling improved error handling and providing clearer feedback when users encounter account balance-related problems.

Review Change Stack

…ser-state (Sentry TAURI-RUST-4ZF)

Add `"insufficient balance"` to the `is_provider_config_rejection_message`
PHRASES array. A user's custom BYO-key DeepSeek provider returns HTTP 402
`{"error":{"message":"… Insufficient Balance …"}}` when their DeepSeek
account balance is exhausted. Pure user-billing state — the remediation
is "top up the DeepSeek account", which Sentry has no way to act on.

This is the same class as the OpenRouter S5 "requires more credits" 402
already in the list — different upstream, different wire token. The
OpenHuman backend's own balance gate is handled separately by
`is_budget_exhausted_message` (different body shape), so there's no
backend false-positive risk: "Insufficient Balance" is a
DeepSeek-specific token our backend never emits.

Tests:
- `detects_insufficient_balance_402_family` — verbatim TAURI-RUST-4ZF
  wire shape (issue 5679, model=ds/deepseek-v4-flash) + bare envelope.
- Existing `ignores_transient_and_server_and_unrelated` still green —
  `"insufficient budget — add credits"` ("budget" ≠ "balance") and the
  other negatives stay unclassified.

## Related

PR tinyhumansai#2796 (Sentry TAURI-RUST-35 family, "does not support tools") also
appends to this PHRASES array but at a different position (end of list
vs. next to the S5 entry) — no merge conflict expected.

## Test plan
- [x] `cargo test detects_insufficient_balance_402_family` — passes
- [x] `cargo test config_rejection` — 7 tests pass, 0 regressions
- [x] `cargo check --bin openhuman-core` — passes
- [x] `cargo fmt --check` — clean
@CodeGhost21 CodeGhost21 requested a review from a team May 28, 2026 04:40
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 82e70dfa-e9b6-44e6-ac87-dfb3b102343d

📥 Commits

Reviewing files that changed from the base of the PR and between 3f2e2f2 and e589727.

📒 Files selected for processing (1)
  • src/openhuman/inference/provider/config_rejection.rs

📝 Walkthrough

Walkthrough

This PR extends the provider configuration-rejection classifier to recognize DeepSeek custom BYO-key 402 failures where the upstream error message contains "Insufficient Balance". The changes include classifier logic updates, module documentation, and test coverage for two wire-shape variants.

Changes

DeepSeek Balance Exhaustion Classification

Layer / File(s) Summary
Balance exhaustion detection and validation
src/openhuman/inference/provider/config_rejection.rs
Module documentation adds the new 4ZF example phrase. The is_provider_config_rejection_message substring matcher adds "insufficient balance" with TAURI-RUST-4ZF context comments. A new test detects_insufficient_balance_402_family validates detection for both truncated custom API error and bare upstream envelope shapes containing this phrase.

Possibly related PRs

  • tinyhumansai/openhuman#2309: Both PRs extend the is_provider_config_rejection_message substring matcher (and related tests) in src/openhuman/inference/provider/config_rejection.rs to classify additional Wave 4 provider-config rejection wire shapes.
  • tinyhumansai/openhuman#2239: Both PRs extend the provider config-rejection detection by updating is_provider_config_rejection_message's substring phrase matching (and its unit tests), with the retrieved PR also wiring that classifier into observability/web/ops.

Suggested labels

working

Suggested reviewers

  • graycyrus
  • senamakel

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes


🐰 A balance so low it cries,
DeepSeek now sees with deeper eyes,
"Insufficient" balance we detect,
Configuration checks—one more correct!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: classifying DeepSeek 'Insufficient Balance' 402 responses as user-state in the configuration rejection classifier.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 28, 2026
@oxoxDev oxoxDev self-assigned this May 28, 2026
Copy link
Copy Markdown
Contributor

@graycyrus graycyrus left a comment

Choose a reason for hiding this comment

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

Clean fix. The "insufficient balance" phrase sits correctly next to the OpenRouter S5 entry — same class of user-billing 402, just a different upstream token. Case-insensitive lowercasing is already in place so capitalisation variants are covered without any extra work.

Two things I verified:

  1. False-positive risk — the PR's claim that the backend never emits this token holds. is_budget_exhausted_message handles our own balance gate with a different body shape, so there's no path where our own error gets misclassified as a provider config rejection.

  2. Test coverage — detects_insufficient_balance_402_family covers both the verbatim Sentry wire body (nested escaping and all) and the bare upstream envelope. The existing negative case "insufficient budget — add credits" confirms the phrase doesn't over-reach. That's the right set of cases.

Approved.

Copy link
Copy Markdown
Contributor

@oxoxDev oxoxDev left a comment

Choose a reason for hiding this comment

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

Author: @CodeGhost21 (MEMBER — core team)

Doc-comment claim "the OpenHuman backend balance gate is handled separately by is_budget_exhausted_message, which never emits this DeepSeek-specific token" is misleading — is_budget_exhausted_message (billing_error.rs:13) also matches "insufficient balance". The two classifiers share this token.

Dispatcher precedence in core/observability.rs::expected_error_kind:

  • L199: is_provider_config_rejection_messageProviderConfigRejection ← runs first, catches this now
  • L205: is_budget_exhausted_messageBudgetExhausted ← previously caught it

Any message containing "insufficient balance" reaching the central dispatcher (agent.run_single / web_channel.run_chat_task re-reports) now classifies as ProviderConfigRejection instead of BudgetExhausted. Backend-direct path (api/rest.rs:582) unaffected — it calls is_budget_exhausted_message independently before expected_error_kind.

Net behavioural impact: both demote to tracing::info! and suppress Sentry, so no event-volume regression. Only observable change is the telemetry kind tag flipping from "budget" to "provider_config_rejection" for the re-reported backend case. Silent shift for any downstream consumer that filters/dashboards on kind.

Requested change — rewrite the doc-comment to acknowledge the overlap + pin the precedence claim:

// TAURI-RUST-4ZF — DeepSeek (custom BYO-key) 402 when the user's
// DeepSeek account balance is exhausted. Body carries the upstream
// `{"error":{"message":"Insufficient Balance",...}}` envelope.
// NOTE: `is_budget_exhausted_message` also matches this token, but
// the `expected_error_kind` dispatcher checks
// `is_provider_config_rejection_message` first (observability.rs:199)
// so this routes to `ProviderConfigRejection` (kind="provider_config_rejection")
// rather than `BudgetExhausted` (kind="budget"). Both demote to
// info-level — no Sentry-volume regression — but downstream consumers
// that filter on the `kind` tag will see the rename.
// Backend balance gate at `api/rest.rs:582` is unaffected:
// it calls `is_budget_exhausted_message` independently before
// `expected_error_kind` is reached.
"insufficient balance",

Also add a dispatcher-level test pinning the precedence:

#[test]
fn insufficient_balance_routes_to_provider_config_rejection_not_budget() {
    // Pins the dispatcher precedence in observability.rs:199 vs 205 —
    // both classifiers match this token, accidental reorder would flip
    // the `kind` tag silently.
    assert_eq!(
        expected_error_kind("Insufficient Balance"),
        Some(ExpectedErrorKind::ProviderConfigRejection),
    );
}

Verified / looks good

  • is_provider_config_rejection_http (ops.rs:506) gates on status {400, 404, 422} only — 402 isn't allowed, so ops.rs-level provider HTTP demotion path unaffected by this token.
  • Web channel caller (web.rs:668) calls is_provider_config_rejection_message directly without status gate — for 4ZF body, demotion is desired.
  • Test covers both wrapped wire shape (verbatim 4ZF) + bare upstream envelope.
  • Case-insensitive matching at the matcher entry — sufficient.
  • 1 file, additions only. coderabbit + graycyrus APPROVED. CI 30/30 green.

Happy to flip to approve once the doc-rewrite + precedence test land.

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

Labels

working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants