Skip to content

fix(agent): suppress empty-provider-response from Sentry (TAURI-RUST-4JX)#2790

Open
CodeGhost21 wants to merge 1 commit into
tinyhumansai:mainfrom
CodeGhost21:fix/sentry-tauri-rust-4jx-empty-response
Open

fix(agent): suppress empty-provider-response from Sentry (TAURI-RUST-4JX)#2790
CodeGhost21 wants to merge 1 commit into
tinyhumansai:mainfrom
CodeGhost21:fix/sentry-tauri-rust-4jx-empty-response

Conversation

@CodeGhost21
Copy link
Copy Markdown
Contributor

@CodeGhost21 CodeGhost21 commented May 27, 2026

Summary

  • Add AgentError::EmptyProviderResponse { iteration } and route agent::harness::session::turn's empty-final-response bail through it, so the existing user-facing string is preserved while the typed variant flows up to run_single.
  • Centralise the "skip Sentry" decision behind a new AgentError::skips_sentry() method covering both MaxIterationsExceeded and the new variant; run_single calls it in place of the inline MaxIterationsExceeded-only check.
  • Keeps Sentry TAURI-RUST-4JX off the radar (~33 events, escalating on 0.56.0) without changing the user-visible error or the AgentError / recoverable semantics.

Why

Latest event payload: Windows user, LM Studio at http://localhost:1234, custom community fine-tune (qwen3.6-27b-heretic-uncensored-finetune-neo-code-di-imatrix-max). Provider response is text_chars=0 thinking_chars=0 tool_calls=0 in 60 ms — completely empty. agent/harness/session/turn.rs:805 then returned:

return Err(anyhow::anyhow!(
    "The model returned an empty response. Please try again."
));

That bubbled to run_single (agent/harness/session/runtime.rs:540), which routes anything other than AgentError::MaxIterationsExceeded through report_error_or_expected("agent", "run_single", …) → Sentry. The result is the same shape as OPENHUMAN-TAURI-99 / -98 (max-iter cap) before that fix: user-state outcome shipped to Sentry as a code-bug.

It's not actionable from Sentry (the user picked a flaky local model), the UI already surfaces the user-facing message, and the deeper "fix" lives in the user's model / provider config. Same call for suppression as the max-iter cap.

What changed

  1. src/openhuman/agent/error.rs — new variant EmptyProviderResponse { iteration: usize } with a Display arm that emits the verbatim user-facing string (so UI surfaces and any external grep/fingerprint contracts hold). New skips_sentry(&self) -> bool method as single source of truth for the suppressed set: today { MaxIterationsExceeded, EmptyProviderResponse }.
  2. src/openhuman/agent/harness/session/turn.rs:805 — replace anonymous anyhow::anyhow! with AgentError::EmptyProviderResponse { iteration: iteration + 1 }.into(). Warn-level breadcrumb that recorded the surfacing decision is preserved.
  3. src/openhuman/agent/harness/session/runtime.rs:540 — replace let is_max_iter = matches!(err.downcast_ref::<AgentError>(), Some(AgentError::MaxIterationsExceeded { .. })) with err.downcast_ref::<AgentError>().is_some_and(AgentError::skips_sentry). Log message generalised to "user-state agent error". Comment updated.
  4. src/openhuman/agent/harness/session/runtime.rs:388 (sanitize_event_error_message) — new arm returning "empty_provider_response" for the Sentry error_kind tag (required by the non-exhaustive match).
  5. src/openhuman/cron/scheduler.rs:37 (agent_error_to_user_message) — new arm returning actionable canned copy: "The model returned an empty response. Try a different model or check your local provider in Settings → AI → LLM." Required by the same non-exhaustive contract; gives cron job failures a useful message instead of the generic fallback.

Tests added (agent::error::tests)

  • empty_provider_response_display_matches_user_facing_string — locks the wire shape against accidental message changes (also a Sentry-fingerprint stability guarantee).
  • skips_sentry_returns_true_for_known_user_state_variantsMaxIterationsExceeded + EmptyProviderResponse.
  • skips_sentry_returns_false_for_real_failures — covers all 7 other variants (ProviderError, ContextLimitExceeded, ToolExecutionError, CostBudgetExceeded, CompactionFailed, PermissionDenied, Other); any future variant that should also suppress must be added to skips_sentry() and this test, both at once.

Test plan

  • cargo test openhuman::agent::error::tests — 9 tests pass (4 new)
  • cargo test openhuman::agent — 751 tests pass, 0 regressions
  • cargo test openhuman::cron::scheduler — 50 tests pass, 0 regressions
  • cargo check --manifest-path Cargo.toml --bin openhuman-core — passes
  • cargo fmt --check on touched files — clean

Post-merge observation: TAURI-RUST-4JX should drop to ~0 events on the next release. The variant still produces structured log::info! lines locally for diagnosability, so a real spike will still be visible in shipped logs (just not in Sentry).

…4JX)

`agent::harness::session::turn` returned an anonymous `anyhow::anyhow!(
"The model returned an empty response. Please try again.")` when the
provider's chat completion contained no text, no thinking, and no tool
calls. That bubbled to `run_single`'s catch-all `report_error_or_expected`
arm and shipped to Sentry as TAURI-RUST-4JX.

The latest event shows the typical trigger: a Windows user running LM
Studio locally with a community fine-tune
(`qwen3.6-27b-heretic-uncensored-finetune-neo-code-di-imatrix-max`) that
returned an empty stream. That's a model / user-config outcome, not an
OpenHuman bug — the UI already surfaces the user-facing string, and
there is no developer remediation path through Sentry.

Mirror the existing `MaxIterationsExceeded` pattern:

1. Add `AgentError::EmptyProviderResponse { iteration }` with a `Display`
   impl that emits the verbatim user-facing string (so UI / fingerprint
   contract is preserved).
2. Replace the anonymous `anyhow!` at `turn.rs:805` with the typed
   variant, retaining the warn-level breadcrumb that records the
   surfacing decision.
3. Introduce `AgentError::skips_sentry()` as the single source of truth
   for which variants get suppressed (`MaxIterationsExceeded` +
   `EmptyProviderResponse`), and call it from `run_single` in place of
   the inline `MaxIterationsExceeded`-only check.
4. Extend `sanitize_event_error_message` (Sentry error_kind tag) and
   `agent_error_to_user_message` (cron job user-facing copy) with arms
   for the new variant — required by the non-exhaustive match contract,
   and gives cron job failures actionable canned copy.

Tests added in `agent::error`:
- `Display` returns the canonical user-facing string (locks the wire
  shape against regressions).
- `skips_sentry()` returns true for both suppressed variants and false
  for every other AgentError variant (positive + negative coverage).

The user still sees the same error, the `Err` still propagates, and
the `AgentError` domain event / `recoverable` semantics are unchanged.
Sentry just stops getting paged for it.

## Test plan
- [x] `cargo test openhuman::agent::error::tests` — 9 tests pass (4 new)
- [x] `cargo test openhuman::agent` — 751 tests pass, 0 regressions
- [x] `cargo test openhuman::cron::scheduler` — 50 tests pass, 0 regressions
- [x] `cargo check --bin openhuman-core` — passes
- [x] `cargo fmt --check` on touched files — clean
@CodeGhost21 CodeGhost21 requested a review from a team May 27, 2026 20:33
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

📝 Walkthrough

Walkthrough

This change introduces typed error classification for degenerate provider responses (no text, no thinking, no tool calls). A new AgentError::EmptyProviderResponse variant with iteration metadata is detected in turn execution, routed through telemetry via a skips_sentry() helper to separate it from true failures, and mapped to user-facing messages in scheduler notifications.

Changes

Empty Provider Response Error Handling

Layer / File(s) Summary
Error Type and Observability Contract
src/openhuman/agent/error.rs
AgentError::EmptyProviderResponse { iteration } variant is added with documentation, Display formatting outputs "The model returned an empty response. Please try again.", and a new public method skips_sentry() returns true for this variant and MaxIterationsExceeded, false otherwise. Tests verify the exact display string and skips_sentry() behavior for suppressed and real-failure variants.
Turn-Level Detection and Error Creation
src/openhuman/agent/harness/session/turn.rs
The empty provider-response failure path returns a typed AgentError::EmptyProviderResponse { iteration: iteration + 1 } instead of a generic anyhow error, enabling type-safe observability downstream.
Telemetry and Event Routing
src/openhuman/agent/harness/session/runtime.rs
sanitize_event_error_message adds a match arm to classify EmptyProviderResponse as "empty_provider_response". run_single generalizes Sentry suppression logic by calling skips_sentry() on the downcast error; when true, it emits structured log::info! instead of reporting through Sentry.
User-Facing Message Mapping
src/openhuman/cron/scheduler.rs
agent_error_to_user_message adds a case for AgentError::EmptyProviderResponse, returning a static notification message indicating the model returned an empty response.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

agent, bug, working

Suggested reviewers

  • M3gA-Mind
  • graycyrus
  • senamakel

Poem

🐰 A response that's empty brings tears to the try,
So we typed it, we routed it, gave users a why.
No Sentry alarms for the model's misstep,
Just logged info softly, with empathy's rep! 🌙

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a new error variant and suppressing it from Sentry reporting, which is the core objective of the PR.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. working A PR that is being worked on by the team. bug labels May 27, 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.

@CodeGhost21 hey! the code looks good to me — clean approach to centralising the Sentry suppression policy behind skips_sentry(), the typed variant is the right call over the anonymous anyhow!, and the test coverage is solid (locking the wire string, both suppressed variants, and all 7 real-failure variants). but there are CI failures on this PR (PR Submission Checklist is failing and most checks are still pending), so i'll hold off on approving until those are green. once CI is clean, i'll come back and approve. let me know if you need any help!

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 working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants