Repository navigation
fix(cron): stop cron billing-state Sentry floods — 402 credits + 400 budget (TAURI-RUST-514 / -BMW) - #3913
Conversation
…inyhumansai#3912) Pull the 402+credit-phrase check out of is_insufficient_credits_event into a public message-level matcher, mirroring is_session_expired_message ↔ is_session_expired_event. Lets the cron retry loop consult the same classifier as the before_send filter (single source of truth). Refs TAURI-RUST-514.
…umansai#3912) A BYO provider insufficient-credits 402 (OpenRouter out of balance) is a permanent billing state with no local lever — retrying it across the backoff loop cannot recover and floods Sentry with per-attempt failure=retries_exhausted reports. Mirror the session-expired halt: classify via is_insufficient_credits_failure (Agent-only), break on the first occurrence, and skip the leaking report_error. Stops the residual at source, independent of which before_send chain runs. Refs TAURI-RUST-514.
…_send (tinyhumansai#3912) tinyhumansai#3617 wired is_insufficient_credits_event into the standalone-CLI before_send (src/main.rs) only. Since tinyhumansai#1061 the shipped desktop product runs the core in-process under Tauri and uses app/src-tauri/src/lib.rs's before_send, which lacked the arm — so the cron agent_job 402 leaked at error level on release builds. Add the matching arm + metadata-only log, closing the drift between the two chains. Refs TAURI-RUST-514.
📝 WalkthroughWalkthroughAdds a shared ChangesInsufficient Credits (402/400) Cron Halt and Sentry Suppression
Sequence DiagramsequenceDiagram
participant Agent as Agent Job
participant Scheduler as Cron Scheduler
participant Classifiers as Billing Classifiers
participant ErrorLog as Error Reporting
participant Sentry as Sentry Client
Agent->>Scheduler: execute_job_with_retry()
Scheduler->>Agent: attempt retry
Agent-->>Scheduler: fail with 402/400 error
Scheduler->>Classifiers: is_insufficient_credits_failure()?
Classifiers-->>Scheduler: true (or is_budget_exhausted)
Scheduler->>Scheduler: set credits_exhausted=true
Scheduler->>Scheduler: break retry loop
Scheduler->>ErrorLog: skip report_error (billing halt)
ErrorLog->>Sentry: log suppression breadcrumb (debug)
Sentry-->>ErrorLog: acknowledged
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2c43bd40eb
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // user-state and must keep reaching Sentry. | ||
| let is_402_status = lower.contains("(402") || lower.contains("402 payment required"); | ||
| is_402_status | ||
| && crate::openhuman::inference::provider::body_indicates_insufficient_credits(text) |
There was a problem hiding this comment.
Check the 402 body before suppressing cron failures
When this new message-level classifier is used by is_insufficient_credits_failure, any formatted provider error like provider API error (402 Payment Required): some unrelated condition is treated as insufficient credits: body_indicates_insufficient_credits also matches the reason phrase payment required, which is part of the status prefix rather than the response body. In that scenario the cron loop now breaks after one attempt and skips the retries_exhausted Sentry report even though the body did not contain a balance/credits signal, contradicting the intended 402+credit-phrase guard.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in 5e4eb50 — is_insufficient_credits_message now slices off the formatted "): " status prefix and runs body_indicates_insufficient_credits against the body only, so the prefix's own "payment required" can't self-match. Added a regression assertion: (402 Payment Required): some unrelated condition → false; (402 Payment Required): … insufficient balance → true.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/openhuman/cron/scheduler.rs`:
- Around line 408-421: Add debug logging to the insufficient_credits_failure
branch within the scheduler to aid production diagnostics. When the
is_insufficient_credits_failure check evaluates to true and credits_exhausted is
set, add a trace or debug log statement that includes stable grep-friendly
prefixes and correlation fields such as job_id, attempt number, and remaining
retries. Ensure the log only contains metadata (no raw provider response body)
and use consistent field names for correlation. Apply the same logging pattern
to any other related terminal branches or report-suppression paths around line
430 to maintain consistency across all new failure-handling flows.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 02590ba6-8ea5-4a6d-aa6a-3a5449c146f9
📒 Files selected for processing (4)
app/src-tauri/src/lib.rssrc/core/observability.rssrc/openhuman/cron/scheduler.rssrc/openhuman/cron/scheduler_tests.rs
…se body The 402-status prefix "(402 Payment Required)" itself contains the phrase "payment required", which body_indicates_insufficient_credits matches — so is_insufficient_credits_message classified ANY 402 (even one with an unrelated body) as insufficient-credits, which would let the cron halt suppress a genuine 402 defect. Slice off the formatted "): " status prefix and run the credit-phrase check against the body only. (codex review on tinyhumansai#3913)
…haustion (TAURI-RUST-BMW)
A cron agent job that hits a permanent managed-backend 400
("Insufficient budget" / USER_INSUFFICIENT_CREDITS) was retried across the
backoff loop and re-reported to Sentry on every exhausted attempt
(domain=cron, operation=agent_job, failure=retries_exhausted; ~3.7k events,
36 users, live on 0.57.18). The tag-gated is_budget_event before_send filter
never matched it — the cron re-report carries no status/non_2xx tag.
Add is_budget_exhausted_failure (Agent-only, sibling of the session-expired
and insufficient-credits-402 halts) keyed on is_budget_exhausted_message;
break on the first occurrence and extend the post-loop guard to skip the
leaking report at source. Add metadata-only debug breadcrumbs to every
billing-state halt/suppress branch (no raw provider/backend body). (coderabbit review)
Minimal first slice of a first-class surface for expected user states (BYO insufficient credits, managed-budget exhaustion from the tinyhumansai#3913 path). These are demoted from Sentry but must stay visible + actionable. - types/userError.ts: thin, privacy-safe UserActionableError contract (i18n keys, never raw provider text). - lib/userErrors/classify.ts: conservative classifier for the two tinyhumansai#3913 states; returns null for anything else. - lib/userErrors/report.ts: defensive ingestion helper (never throws). - store/userErrorsSlice.ts (+ selectors): in-memory, dedupes by kind:scope:provider with count/last-seen; resolve/dismiss; clears on user switch (privacy). - components/userErrors/UserErrorCenter.tsx: shell-mounted panel + badge, deep-links billing / provider settings; survives route changes. - ChatRuntimeProvider.onError: additive producer wiring. - i18n: 9 keys across all 14 locales (parity-safe). - Vitest: classifier, slice/selectors, ingestion, panel render/route/dismiss. Core structured emission, durable persistence, cron producer, and richer kinds are follow-ups (see PR body). Closes tinyhumansai#3931
…yhumansai#4404) TAURI-RUST-HXF: when a tick's provider config keeps rejecting with a permanent per-minute token cap (413/TPM), the loop re-fired the doomed request every 5-30 min and re-reported it — 2232 events from one user, the cron-billing-flood family (tinyhumansai#3913). Add a circuit breaker keyed on the Subconscious provider signature: on a permanent rate-cap agent error, halt the agent run; skip subsequent ticks while the same config is set; auto-clear the moment the user switches model/provider/tier. Mirrors the existing tool-capability (TAURI-RUST-ADC) permanent-failure arm. In-memory only — a restart re-probes once, then re-halts. Pure helpers unit-tested.
…budget (TAURI-RUST-514 / -BMW) (tinyhumansai#3913)\n
Summary
agent_jobretry loop from retrying permanent billing user-states and reporting them to Sentry on every exhausted attempt — two sibling Sentry floods:USER_INSUFFICIENT_CREDITS("Insufficient budget").is_insufficient_credits_eventarm to the desktopbefore_sendchain (app/src-tauri/src/lib.rs) — fix(inference): cap extraction max_tokens + classify provider 402 to stop the retry/Sentry storm (#3616) #3617 wired it into the standalone-CLI chain (src/main.rs) only.is_insufficient_credits_messagematcher (one source of truth with thebefore_sendfilter); reuses the existingprovider::is_budget_exhausted_messagefor the budget case.Problem
Both issues are the same leak path: every event is tagged
domain=cron,operation=agent_job,failure=retries_exhausted.execute_job_with_retryreports the propagated billing error with a bare, unconditionalreport_error. Session-expired is halted-on-first right above it; the two billing states were not — so a permanent 402/400 is retried N times and reported each exhaustion.The respective
before_sendfilters do not catch the cron re-report:is_insufficient_credits_eventwas wired intosrc/main.rsonly; the shipped desktop core (in-process since fix(core,cef): run core in-process and stop orphaning CEF helpers on Cmd+Q #1061) usesapp/src-tauri/src/lib.rs's chain, which lacked it.is_budget_eventis tag-gated (failure=non_2xx+status=400) — tags the cron re-report (failure=retries_exhausted, nostatus) does not carry.514 is also the live residual of #3617 (which capped the extraction path; these are the full-window cron agent requests #3617 did not cover).
Solution
is_insufficient_credits_message(&str);is_insufficient_credits_eventdelegates to it. The matcher checks the credit phrase against the response body only (strips the formatted"): "status prefix) so the 402 reason phrase "Payment Required" can't masquerade as a body credit-signal (Codex P2).is_insufficient_credits_failure(402) andis_budget_exhausted_failure(400), both Agent-only, mirroringis_session_expired_failure. Break on the first occurrence; extend the post-loop guard to skip the report on either billing halt. Metadata-only debug breadcrumbs on the halt + suppression branches (CodeRabbit). Fixes the leak and the retry amplification at source, independent of the tag-gatedbefore_sendfilters.is_insufficient_credits_eventarm + metadata-only log to the desktopbefore_send, closing the fix(inference): cap extraction max_tokens + classify provider 402 to stop the retry/Sentry storm (#3616) #3617 drift.Bucket (RCA-vs-suppress):
unpreventable— a BYO account out of credit / a managed account out of budget has no local lever to fund it, so demotion is the complete answer for the billing error itself; the real lever is on the amplification (don't retry a permanent 402/400), which the cron halt fixes. No suppression on a data path. Matchers anchored on status + phrase, guarded by verbatim-body tests so wording drift fails CI.Submission Checklist
docs/TEST-COVERAGE-MATRIX.mdCloses #NNNin the## RelatedsectionImpact
failure=retries_exhaustedas before.Related
src/main.rsandapp/src-tauri/src/lib.rsbefore_sendchains into one shared builder.AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
Commit & Branch
fix/514-cron-insufficient-creditsd8300ebb46e0e373e06337a1320bde5842f12eccValidation Run
pnpm --filter openhuman-app format:check— no frontend/TS files changedpnpm typecheck— no TS files changedcargo test --lib -- is_insufficient_credits is_budget_exhausted_failure→ 11 passedcargo fmt+GGML_NATIVE=OFF cargo check(root) cleanGGML_NATIVE=OFF pnpm rust:check(app/src-tauri) cleanValidation Blocked
command:N/Aerror:N/Aimpact:N/ABehavior Changes
Parity Contract
failure=retries_exhausted; session-expired halt untouched; event-filter behavior unchanged except the Codex P2 body-scoping (stricter, not looser).Duplicate / Superseded PR Handling