Skip to content

fix(cron): stop cron billing-state Sentry floods — 402 credits + 400 budget (TAURI-RUST-514 / -BMW) - #3913

Merged
senamakel merged 5 commits into
tinyhumansai:mainfrom
oxoxDev:fix/514-cron-insufficient-credits
Jun 22, 2026
Merged

senamakel merged 5 commits into
tinyhumansai:mainfrom
oxoxDev:fix/514-cron-insufficient-credits

Conversation

@oxoxDev

@oxoxDev oxoxDev commented Jun 22, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Stops the cron agent_job retry loop from retrying permanent billing user-states and reporting them to Sentry on every exhausted attempt — two sibling Sentry floods:
    • TAURI-RUST-514 — BYO provider 402 ("requires more credits", OpenRouter out of balance).
    • TAURI-RUST-BMW — managed-backend 400 USER_INSUFFICIENT_CREDITS ("Insufficient budget").
  • Adds the is_insufficient_credits_event arm to the desktop before_send chain (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.
  • Extracts a shared is_insufficient_credits_message matcher (one source of truth with the before_send filter); reuses the existing provider::is_budget_exhausted_message for 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_retry reports the propagated billing error with a bare, unconditional report_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_send filters do not catch the cron re-report:

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

  • observability.rs — extract is_insufficient_credits_message(&str); is_insufficient_credits_event delegates 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).
  • cron/scheduler.rs — add is_insufficient_credits_failure (402) and is_budget_exhausted_failure (400), both Agent-only, mirroring is_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-gated before_send filters.
  • app/src-tauri/lib.rs — add the is_insufficient_credits_event arm + metadata-only log to the desktop before_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.

Follow-up (out of scope): the two before_send chains (src/main.rs and app/src-tauri/src/lib.rs) can silently diverge — worth unifying into one shared builder later.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — Coverage Gate (diff-cover) green on this PR.
  • N/A: behaviour-only change (Sentry noise routing) — no feature rows added/removed/renamed in docs/TEST-COVERAGE-MATRIX.md
  • N/A: no coverage-matrix feature IDs touched by this change
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • N/A: no release-cut surface touched (provider-error Sentry routing only)
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Desktop (in-process core): cron agent jobs that hit a permanent BYO 402 or managed-backend budget 400 now stop after the first attempt instead of looping the backoff, and the event is demoted from Sentry on the shipped build. Recoverable failures unchanged — they retry and report failure=retries_exhausted as before.
  • No schema, migration, or API change. No new dependencies.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/514-cron-insufficient-credits
  • Commit SHA: d8300ebb46e0e373e06337a1320bde5842f12ecc

Validation Run

  • N/A: pnpm --filter openhuman-app format:check — no frontend/TS files changed
  • N/A: pnpm typecheck — no TS files changed
  • Focused tests: cargo test --lib -- is_insufficient_credits is_budget_exhausted_failure → 11 passed
  • Rust fmt/check (if changed): cargo fmt + GGML_NATIVE=OFF cargo check (root) clean
  • Tauri fmt/check (if changed): GGML_NATIVE=OFF pnpm rust:check (app/src-tauri) clean

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: permanent BYO-provider 402 / managed-backend budget 400 in a cron agent job halts after one attempt and is kept out of Sentry on the desktop build.
  • User-visible effect: none (background cron + observability routing only); recoverable failures unchanged.

Parity Contract

  • Legacy behavior preserved: non-billing failures still retry and report failure=retries_exhausted; session-expired halt untouched; event-filter behavior unchanged except the Codex P2 body-scoping (stricter, not looser).
  • Guard/fallback/dispatch parity checks: matchers require status anchor + genuine phrase; Agent-only scope (shell jobs keep retry semantics); both before_send chains now carry the credits arm.

Duplicate / Superseded PR Handling

oxoxDev added 3 commits June 22, 2026 17:14
…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.
@oxoxDev
oxoxDev requested a review from a team June 22, 2026 11:46
@coderabbitai

coderabbitai Bot commented Jun 22, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a shared is_insufficient_credits_message predicate in observability.rs anchored to a 402 status shape and a provider credit phrase. The cron scheduler uses it to halt agent retries immediately on 402 insufficient credits or 400 budget exhausted, suppressing the residual report_error call. The Tauri shell before_send filter gains the matching event-drop arm that was previously missing from the desktop process.

Changes

Insufficient Credits (402/400) Cron Halt and Sentry Suppression

Layer / File(s) Summary
is_insufficient_credits_message classifier and event filter refactor
src/core/observability.rs
Adds public is_insufficient_credits_message predicate requiring both a 402 status shape and the provider credit-indicator phrase; refactors is_insufficient_credits_event to delegate to it for event.message and all event.exception value paths; extends unit tests for match, rejection, and delegation parity.
Scheduler 402/400 halt-on-first and suppress report_error
src/openhuman/cron/scheduler.rs, src/openhuman/cron/scheduler_tests.rs
Adds is_insufficient_credits_failure (402, agent-only, checks last_agent_error then last_output) and is_budget_exhausted_failure (400, agent-only, same fallback); introduces credits_exhausted and budget_exhausted flags in execute_job_with_retry; breaks the retry loop early on either condition; gates the final report_error behind !credits_exhausted && !budget_exhausted. Tests cover primary match, last_output fallback, negative guards (canned messages, 500/400 unrelated shapes), and scope restriction to JobType::Agent.
Tauri shell before_send filter wiring
app/src-tauri/src/lib.rs
Adds the missing is_insufficient_credits_event arm to the desktop process before_send filter, logging by event id and returning None to drop the event.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

agent

Suggested reviewers

  • graycyrus

🐇 A 402 knocked, then a 400 came to stay,
Retries just multiplied grief day by day.
Now credits_exhausted and budgets must align,
The scheduler halts first, the before_send draws the line.
No flood in Sentry—just logs when they pay. 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed All code changes directly address the requirements in #3912: adding insufficient-credits detection to observability, halting cron retries on billing errors, and extending the before_send filter in desktop client.
Out of Scope Changes check ✅ Passed All changes are directly related to addressing billing-state Sentry floods: observability matcher refactoring, cron scheduler retry logic, before_send filter alignment, and comprehensive test coverage.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly matches the PR's primary objective: stopping Sentry floods from billing-state errors (402 insufficient credits, 400 budget exhaustion) by preventing cron retries and filtering events.

✏️ 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 bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. sentry-traced-bug Bug identified via Sentry triage labels Jun 22, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread src/core/observability.rs Outdated
// 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between d4db739 and 2c43bd4.

📒 Files selected for processing (4)
  • app/src-tauri/src/lib.rs
  • src/core/observability.rs
  • src/openhuman/cron/scheduler.rs
  • src/openhuman/cron/scheduler_tests.rs

Comment thread src/openhuman/cron/scheduler.rs Resolved
oxoxDev added 2 commits June 22, 2026 18:55
…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)
@oxoxDev oxoxDev changed the title fix(cron): stop cron 402 insufficient-credits Sentry flood (TAURI-RUST-514) fix(cron): stop cron billing-state Sentry flood — BYO 402 + managed 400 (TAURI-RUST-514 / -BMW) Jun 22, 2026
@coderabbitai coderabbitai Bot added the agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. label Jun 22, 2026
@oxoxDev oxoxDev changed the title fix(cron): stop cron billing-state Sentry flood — BYO 402 + managed 400 (TAURI-RUST-514 / -BMW) fix(cron): stop cron billing-state Sentry floods — 402 credits + 400 budget (TAURI-RUST-514 / -BMW) Jun 22, 2026
@senamakel
senamakel merged commit bc49ef3 into tinyhumansai:main Jun 22, 2026
29 of 34 checks passed
M3gA-Mind added a commit to M3gA-Mind/openhuman that referenced this pull request Jun 23, 2026
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
oxoxDev added a commit to oxoxDev/openhuman that referenced this pull request Jul 2, 2026
…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.
senamakel pushed a commit to nocstah/openhuman that referenced this pull request Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. sentry-traced-bug Bug identified via Sentry triage

Projects

None yet

2 participants