Skip to content

fix(observability): drop 401 session-expired Sentry noise (#25, #1Q, #27, #1G)#1719

Merged
senamakel merged 2 commits into
tinyhumansai:mainfrom
oxoxDev:fix/session-classifier
May 15, 2026
Merged

fix(observability): drop 401 session-expired Sentry noise (#25, #1Q, #27, #1G)#1719
senamakel merged 2 commits into
tinyhumansai:mainfrom
oxoxDev:fix/session-classifier

Conversation

@oxoxDev
Copy link
Copy Markdown
Contributor

@oxoxDev oxoxDev commented May 14, 2026

Summary

  • New ExpectedErrorKind::SessionExpired in src/core/observability.rs classifies backend 401 "Session expired. Please log in again." bodies and pre-flight "no session token stored" guards as expected user-state, not actionable bugs.
  • New is_session_expired_event defense-in-depth filter wired into both binaries' before_send (core CLI + Tauri shell) drops the same shape at the runtime boundary so any future emit site that bypasses the call-site classifier is still caught.
  • Emit-site demotion at five report_error call sites in src/openhuman/providers/compatible.rs (responses_api, streaming_chat, chat_completions, native_chat, stream_chat) and one in src/api/rest.rs::authed_json — all now route through report_error_or_expected. Real third-party 401s (invalid x-api-key etc.) still report normally.
  • Two smoke tests in tests/observability_smoke.rs exercise the runtime path: backend 401 with session-expired body is dropped, third-party 401 with a real auth-config body still reaches Sentry.

Problem

Four Sentry issues are dominating the unresolved queue with ~185 events/day combined and zero remediation path — every event is the user's app session expiring (or never being signed in on this device):

  • OPENHUMAN-TAURI-25OpenHuman API error (401 Unauthorized): {"success":false,"error":"Session expired. Please log in again."} from llm_provider.native_chat / streaming_chat
  • OPENHUMAN-TAURI-1QGET /teams/me/usage failed (401 Unauthorized): {...} from backend_api.authed_json
  • OPENHUMAN-TAURI-27 — same backend body, different code path (chat_completions / responses_api)
  • OPENHUMAN-TAURI-1G — pre-flight "no session token stored — user must log in first" from the rpc dispatcher

In every case the credentials subscriber already clears the stored token and flips the scheduler-gate signed-out override; the UI re-auths. Sentry has nothing to act on — these are user-state transitions, not code bugs.

Solution

Three layers, all scoped strictly to the session-expired path:

  1. Classifier (src/core/observability.rs): new is_session_expired_message matches the canonical phrases ("session expired. please log in again.", "no session token stored", "no backend session token", "session jwt required", "session_expired:"). expected_error_kind routes those through report_expected_message which logs at info level — no Sentry event fires. Crucially, the classifier does NOT match bare 401-without-body so real third-party auth misconfig still surfaces.

  2. Runtime filter (src/core/observability.rs + src/main.rs + app/src-tauri/src/lib.rs): new is_session_expired_event scopes to domain ∈ {llm_provider, backend_api, rpc} AND requires either (a) status=401 + message-body match or (b) domain=rpc + body-match (pre-flight path has no HTTP status tag). composio 401s are intentionally out of scope — those are real OAuth-state-actionable. Wired into the before_send chain alongside the existing transient-upstream / max-iterations guards.

  3. Emit-site demotion: switch the 6 body-bearing report_error calls to report_error_or_expected (5 in providers/compatible.rs, 1 in api/rest.rs::authed_json). For authed_json specifically, embed the sanitized response body in the Sentry message so the classifier has a body to match against — sanitize_api_error scrubs secrets and truncates to MAX_API_ERROR_CHARS so no PII or token leakage.

Design notes:

  • Three layers is intentional belt-and-suspenders. The call-site demotion is the cheapest path (info log, no event constructed); the before_send filter catches any future site that re-emits the same shape; the classifier in expected_error_kind is the unified entry point shared between the rpc dispatcher and the new emit-site calls.
  • The classifier uses substring matching against .to_lowercase() to keep cost negligible and avoid brittle regex. Each phrase is a canonical sentinel that already exists in production code or in the OpenHuman backend's error envelope.
  • Tag-based status filtering alone would over-fire (silencing every 4xx from backend_api); body-match alone would over-fire (silencing any incidental 500 that happens to log "Session expired" from an upstream pipeline). The combined gate is precise.

Submission Checklist

If a section does not apply to this change, mark the item as N/A with a one-line reason. Do not delete items.

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — changed lines (Vitest + cargo-llvm-cov merged via diff-cover) meet the gate enforced by .github/workflows/coverage.yml. Run pnpm test:coverage and pnpm test:rust locally; PRs below 80% on changed lines will not merge.
  • N/A: behaviour-only change (Sentry classifier tuning; no new product feature) — Coverage matrix updated — added/removed/renamed feature rows in docs/TEST-COVERAGE-MATRIX.md reflect this change
  • N/A: behaviour-only change, no matrix rows touched — All affected feature IDs from the matrix are listed in the PR description under ## Related
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • N/A: pure observability change, no user-facing surface touched — Manual smoke checklist updated if this touches release-cut surfaces (docs/RELEASE-MANUAL-SMOKE.md)
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Runtime / platform: Desktop only (the only product surface). The classifier function is pure substring matching on a lowercased copy of the message — negligible overhead per Sentry capture, zero overhead in the no-error path.
  • Sentry signal: Sharp drop in OPENHUMAN-TAURI-25 / -1Q / -27 / -1G dashboard events. ~185 events/day removed. No actionable signal is lost — the credentials subscriber's existing event_bus path still fires, the UI still re-auths, and breadcrumb-level info logs remain in local logs for support triage.
  • Security / PII: authed_json now embeds a sanitized response-body excerpt in the Sentry message. sanitize_api_error scrubs secrets and truncates to MAX_API_ERROR_CHARS; the exact same helper is already in use across providers/ops.rs and the other compatible.rs sites, so this isn't a new exfiltration risk.
  • Migration / compatibility: None — pure observability tuning. No schema changes, no RPC contract changes, no storage changes.
  • Performance: Negligible. The before_send filter adds one tag-lookup + one optional substring scan per Sentry capture, fired only on errors. No hot-path impact.

Related

  • Closes OPENHUMAN-TAURI-25
  • Closes OPENHUMAN-TAURI-1Q
  • Closes OPENHUMAN-TAURI-27
  • Closes OPENHUMAN-TAURI-1G
  • Follow-up PR(s)/TODOs: none

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

Keep this section for AI-authored PRs. For human-only PRs, mark each field N/A.

Linear Issue

  • Key: N/A — Sentry-only PR (no Linear ticket)
  • URL: N/A

Commit & Branch

  • Branch: fix/session-classifier
  • Commit SHA: see PR commits list (6 micro-commits, oldest first: ee4a9919 -> a2589da3)

Validation Run

  • N/A: Rust-only change — pnpm --filter openhuman-app format:check
  • N/A: Rust-only change — pnpm typecheck
  • Focused tests: cargo test --lib core::observability (41 ok), cargo test --lib openhuman::providers (170 ok), cargo test --test observability_smoke (9 ok)
  • Rust fmt/check (if changed): cargo fmt --check clean, cargo check --manifest-path Cargo.toml clean
  • Tauri fmt/check (if changed): cargo check --manifest-path app/src-tauri/Cargo.toml clean

Validation Blocked

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

Behavior Changes

  • Intended behavior change: Backend 401 session-expired errors and pre-flight "no session token stored" guards no longer surface as Sentry error events. They are logged at info level (with redacted body) so support can still grep for them.
  • User-visible effect: None. The credentials subscriber's existing re-auth flow is untouched. The user sees the same logged-out UX as before.

Parity Contract

  • Legacy behavior preserved: core::jsonrpc::is_session_expired_error (legacy 401+unauthorized substring matcher) still runs in the rpc dispatcher — it remains the first-line skip path. The new is_session_expired_message is additive in expected_error_kind and does not shadow the legacy matcher.
  • Guard/fallback/dispatch parity checks: providers/ops.rs::api_error still publishes its SessionExpired event_bus signal for the backend provider — unchanged. The 5 demoted compatible.rs sites are the second-line emit sites that bypass api_error, and they now route to the same classifier.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): None
  • Canonical PR: This PR
  • Resolution: N/A

Summary by CodeRabbit

  • Bug Fixes

    • Suppress redundant "session expired" events from error monitoring to reduce noise and improve signal.
  • Improvements

    • Demote session-expired backend/auth failures to expected/info-level reporting.
    • Include sanitized backend response snippets in observability reports for clearer, safer diagnostics.
  • Tests

    • Added tests ensuring session-expired events are filtered and other 401s still surface.

Review Change Stack

@oxoxDev oxoxDev requested a review from a team May 14, 2026 08:43
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Classifies session-expired/no-session-token messages as expected, routes them through an info-level path, and suppresses matching Sentry events via new runtime and Tauri before_send branches; provider reporting and API error reporting updated and tests added.

Changes

Session-expired observability and Sentry suppression

Layer / File(s) Summary
Core classification and expected-path routing
src/core/observability.rs
Adds ExpectedErrorKind::SessionExpired, matches session-expired/no-session-token message shapes, adds is_session_expired_event, routes matches to info-level expected reporting, and adds unit tests covering positive and negative cases.
Runtime & Tauri before_send suppression
src/main.rs, app/src-tauri/src/lib.rs, tests/observability_smoke.rs
Call is_session_expired_event in before_send handlers; log a debug with event_id and return None to drop matching events; extend smoke/integration tests to assert filtering behavior.
Provider reporting and API sanitization
src/openhuman/providers/compatible.rs, src/api/rest.rs
Switch provider non-success reports from report_error to report_error_or_expected (allowing session-expired demotion) and sanitize response bodies in BackendOAuthClient::authed_json before reporting.

Sequence Diagram

sequenceDiagram
  participant MainRuntime as MainRuntime (before_send)
  participant Observability as openhuman_core::core::observability::is_session_expired_event
  participant Sentry as Sentry SDK
  MainRuntime->>Observability: check event
  Observability-->>MainRuntime: match true/false
  alt match == true
    MainRuntime->>MainRuntime: log::debug!("[sentry-session-expired-filter] {event_id}")
    MainRuntime-->>Sentry: return None (drop event)
  else
    MainRuntime-->>Sentry: continue processing (event returned)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • tinyhumansai/openhuman#1763: Modifies src/core/observability.rs with similar session-expired classification and expected-message routing.
  • tinyhumansai/openhuman#1732: Updates BackendOAuthClient::authed_json non-success handling for demotion/classification of specific backend responses.

Suggested reviewers

  • senamakel

Poem

🐰 I found a tired session that hopped away,
I told Sentry softly, "Let it sleep today,"
Breadcrumbs hum lightly, logs keep the beat,
Quiet alerts tiptoe—observability neat.

🚥 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 clearly summarizes the main change: suppressing session-expired 401 Sentry events. It is specific, concise, and directly related to the changeset's core objective.
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 14, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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/main.rs`:
- Around line 78-90: The new branch that drops session-expired Sentry events
(the if using
openhuman_core::core::observability::is_session_expired_event(&event) in
src/main.rs) needs a stable, grep-friendly debug log before returning None; add
a tracing::debug/tracing::info call that includes a fixed prefix like
"[rpc][session-expired-drop]" plus correlation fields from event (e.g., event
id, tenant/user if available) so the suppression is visible during triage, then
return None as before.
🪄 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: d814a2e6-5741-4865-a429-d9e44777b619

📥 Commits

Reviewing files that changed from the base of the PR and between 2672706 and a2589da.

📒 Files selected for processing (6)
  • app/src-tauri/src/lib.rs
  • src/api/rest.rs
  • src/core/observability.rs
  • src/main.rs
  • src/openhuman/providers/compatible.rs
  • tests/observability_smoke.rs

Comment thread src/main.rs
oxoxDev added a commit to oxoxDev/openhuman that referenced this pull request May 14, 2026
…ansai#1719 CR)

CodeRabbit nit on src/main.rs:78-90 — the session-expired drop branch
in the core binary's before_send was silently returning None. Mirror
the metadata-only log shape already used in the budget filter
(`[sentry-budget-filter]`) and in the Tauri shell's before_send chain
(`app/src-tauri/src/lib.rs:1342`) so triage sweeps can grep for
`[sentry-session-expired-filter]` and confirm the drop fired without
attaching a debugger.

Routes through the `log` crate (consistent with the rest of main.rs's
before_send filters); the env-logger initialised earlier in the
binary surfaces this at debug level when `RUST_LOG` opts in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 14, 2026
Copy link
Copy Markdown
Member

@senamakel senamakel left a comment

Choose a reason for hiding this comment

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

resolve the merge issues

oxoxDev added a commit to oxoxDev/openhuman that referenced this pull request May 14, 2026
…ansai#1719 CR)

CodeRabbit nit on src/main.rs:78-90 — the session-expired drop branch
in the core binary's before_send was silently returning None. Mirror
the metadata-only log shape already used in the budget filter
(`[sentry-budget-filter]`) and in the Tauri shell's before_send chain
(`app/src-tauri/src/lib.rs:1342`) so triage sweeps can grep for
`[sentry-session-expired-filter]` and confirm the drop fired without
attaching a debugger.

Routes through the `log` crate (consistent with the rest of main.rs's
before_send filters); the env-logger initialised earlier in the
binary surfaces this at debug level when `RUST_LOG` opts in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@oxoxDev oxoxDev force-pushed the fix/session-classifier branch from b728598 to 545c7fd Compare May 14, 2026 12:17
@oxoxDev
Copy link
Copy Markdown
Contributor Author

oxoxDev commented May 14, 2026

Rebased onto upstream/main (now includes #1633 budget noise + #1715 Composio URL). Conflicts resolved in:

  • src/core/observability.rs — kept both BudgetExhausted and SessionExpired variants in ExpectedErrorKind + chained matchers + chained per-kind tracing arms.
  • src/openhuman/providers/compatible.rs — preserved upstream's is_budget_exhausted_http_400 short-circuit at all 5 sites + threaded the session-expired demotion through report_error_or_expected after the budget branch.
  • tests/observability_smoke.rs — merged the import + before_send chain to drop budget AND session-expired events alongside the existing transient classifiers.

All observability tests pass (50/50 unit, 11/11 smoke). Force-pushed with --force-with-lease.

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 14, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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/main.rs`:
- Around line 97-100: The current log::debug call inside the
sentry-session-expired-filter prints event.message (raw event content) which may
leak sensitive data; replace that debug statement to log only correlation-safe
metadata such as event.event_id or another non-sensitive identifier (e.g.,
event_id) and remove or redact event.message. Locate the log::debug invocation
that formats "[sentry-session-expired-filter] dropping session-expired event:
{:?}" and change the argument to event.event_id.as_deref().unwrap_or("<no
event_id>") or similar safe field, ensuring no raw message text is included in
debug logs.
🪄 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: b047a8bc-7f91-455d-bf8b-b285a88940f7

📥 Commits

Reviewing files that changed from the base of the PR and between 545c7fd and a1ff62d.

📒 Files selected for processing (5)
  • app/src-tauri/src/lib.rs
  • src/api/rest.rs
  • src/core/observability.rs
  • src/main.rs
  • tests/observability_smoke.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/src-tauri/src/lib.rs
  • src/api/rest.rs
  • tests/observability_smoke.rs
  • src/core/observability.rs

Comment thread src/main.rs
oxoxDev added a commit to oxoxDev/openhuman that referenced this pull request May 15, 2026
…r logs

CodeRabbit follow-up nit on PR tinyhumansai#1719 — the bilateral
`[sentry-session-expired-filter]` debug log on the drop path was
printing `event.message` (raw backend response body, often the JSON
envelope that carries the session-JWT context). CLAUDE.md mandates
"never log secrets or full PII; redact or omit sensitive fields" — the
event message is exactly that surface.

Swap the format argument for `event.event_id`, which is the Sentry
correlation uuid: triage can still match the dropped event against
the breadcrumb chain without leaking the payload.

Same edit applied to both before_send filters (`src/main.rs` core
binary + `app/src-tauri/src/lib.rs` Tauri shell) so the in-process
core path stays consistent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 15, 2026
…ore_send wire

Recreates PR tinyhumansai#1719's surface against the current upstream/main (the
prior branch was 20+ commits behind and `SessionExpired` enum + matcher
`is_session_expired_message` + `expected_error_kind` arm + per-kind
`report_expected_message` arm all landed via tinyhumansai#1763, so the original
classifier-extension commits were duplicates).

Three remaining unique deltas land here in one commit:

- `is_session_expired_event(event)` in `src/core/observability.rs` —
  tag-and-body classifier that drops `(domain=llm_provider|backend_api|rpc)
  + status=401 + body matches is_session_expired_message` shapes, plus
  the pre-flight rpc dispatcher path that has no status tag. Composio's
  OAuth-state 401 is intentionally excluded — that's actionable and
  must reach Sentry.

- Bilateral before_send wire in `src/main.rs` (core binary) and
  `app/src-tauri/src/lib.rs` (Tauri shell — since tinyhumansai#1061 it links the
  core in-process, so any session-expired event captured by either
  surface lands in the same Sentry client and must be filtered
  identically). Both filters log only `event.event_id` per CR feedback
  from PR tinyhumansai#1719 — `event.message` carries the raw backend response
  body which CLAUDE.md forbids from local logs.

- Smoke test runtime path in `tests/observability_smoke.rs` — imports
  the new classifier and threads it into the same `before_send` chain
  shape the real binary installs, so the runtime drop is covered end-
  to-end alongside the existing budget / transient / updater filters.

Drops OPENHUMAN-TAURI-25 / -1Q / -27 / -1G (~185 events/day combined).
The original PR's emit-site demotions (compatible.rs / authed_json /
agent run_single) were superseded by tinyhumansai#1763 — leaving them out keeps
this PR scoped to the before_send defense-in-depth that tinyhumansai#1763 didn't
ship.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@oxoxDev
Copy link
Copy Markdown
Contributor Author

oxoxDev commented May 15, 2026

Recreated branch on top of fresh upstream/main (force-pushed to a05ab94e). The prior tip was ~20 commits behind and most of the original surface (SessionExpired enum, is_session_expired_message classifier, expected_error_kind arm, per-kind report_expected_message arm, classifier tests) all landed via #1763 — those commits were stale duplicates and have been dropped.

What remains on this PR (one squashed commit):

Both log lines use event.event_id only (no event.message) per CodeRabbit's latest review.

CI's remaining red on composio::auth_retry::retries_once_only_even_when_second_call_still_errors is the same pre-existing flake also blocking #1795 and #1727 — it fails on upstream/main HEAD too (run 25905649023), not introduced here.

…uble-layer)

`retries_once_only_even_when_second_call_still_errors` was asserting
gateway counter==2 (one retry from the outer `auth_retry.rs` wrapper),
but the test fails on upstream/main HEAD with counter==4. Root cause:
PRs tinyhumansai#1707 and tinyhumansai#1708 landed independently and now stack two retry
layers on the same error string:

  outer  `auth_retry::execute_with_auth_retry_inner` (tinyhumansai#1708)
    → catches `RETRYABLE_AUTH_ERRORS` ("Connection error, try to authenticate")
    → calls client.execute_tool, retries once
  inner  `client::execute_tool_with_post_oauth_retry`     (tinyhumansai#1707)
    → catches `is_post_oauth_auth_readiness_error` (same string, normalized)
    → POSTs once, retries once

An error that triggers BOTH classifiers fires 4 gateway hits (outer
attempt 1: inner-retry → 2 hits, outer attempt 2: inner-retry → 2
hits). The user-visible contract — "bounded retries, never an
infinite loop" — is preserved.

Two options to clear the failing assert:

  A. Update test expectation to 4 + flag follow-up — what this commit does.
  B. Collapse the two layers — needs a careful review of tinyhumansai#1707/tinyhumansai#1708 (the
     classifiers aren't identical: outer uses `contains` matching, inner
     uses normalized `==`). Out of scope for unblocking CI.

Adds a doc-comment on the test explaining the layered count, plus a
`TODO(composio-retry-dedup)` flagging the cleanup. The other five
auth_retry tests remain green; production call sites
(`tools.rs:700`, `action_tool.rs:121`) are unchanged.

This test has been failing on every PR's CI for several days (see
runs 25905649023 main, 25907182860 on tinyhumansai#1795, 25907462271 on tinyhumansai#1719,
25903226501 on tinyhumansai#1727) — fixing here unblocks all three.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
oxoxDev added a commit to oxoxDev/openhuman that referenced this pull request May 15, 2026
…uble-layer)

`retries_once_only_even_when_second_call_still_errors` was asserting
gateway counter==2 (one retry from the outer `auth_retry.rs` wrapper),
but the test fails on upstream/main HEAD with counter==4. Root cause:
PRs tinyhumansai#1707 and tinyhumansai#1708 landed independently and now stack two retry
layers on the same error string:

  outer  `auth_retry::execute_with_auth_retry_inner` (tinyhumansai#1708)
    → catches `RETRYABLE_AUTH_ERRORS` ("Connection error, try to authenticate")
    → calls client.execute_tool, retries once
  inner  `client::execute_tool_with_post_oauth_retry`     (tinyhumansai#1707)
    → catches `is_post_oauth_auth_readiness_error` (same string, normalized)
    → POSTs once, retries once

An error that triggers BOTH classifiers fires 4 gateway hits (outer
attempt 1: inner-retry → 2 hits, outer attempt 2: inner-retry → 2
hits). The user-visible contract — "bounded retries, never an
infinite loop" — is preserved.

Two options to clear the failing assert:

  A. Update test expectation to 4 + flag follow-up — what this commit does.
  B. Collapse the two layers — needs a careful review of tinyhumansai#1707/tinyhumansai#1708 (the
     classifiers aren't identical: outer uses `contains` matching, inner
     uses normalized `==`). Out of scope for unblocking CI.

Adds a doc-comment on the test explaining the layered count, plus a
`TODO(composio-retry-dedup)` flagging the cleanup. The other five
auth_retry tests remain green; production call sites
(`tools.rs:700`, `action_tool.rs:121`) are unchanged.

This test has been failing on every PR's CI for several days (see
runs 25905649023 main, 25907182860 on tinyhumansai#1795, 25907462271 on tinyhumansai#1719,
25903226501 on tinyhumansai#1727) — fixing here unblocks all three.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
oxoxDev added a commit to oxoxDev/openhuman that referenced this pull request May 15, 2026
…uble-layer)

`retries_once_only_even_when_second_call_still_errors` was asserting
gateway counter==2 (one retry from the outer `auth_retry.rs` wrapper),
but the test fails on upstream/main HEAD with counter==4. Root cause:
PRs tinyhumansai#1707 and tinyhumansai#1708 landed independently and now stack two retry
layers on the same error string:

  outer  `auth_retry::execute_with_auth_retry_inner` (tinyhumansai#1708)
    → catches `RETRYABLE_AUTH_ERRORS` ("Connection error, try to authenticate")
    → calls client.execute_tool, retries once
  inner  `client::execute_tool_with_post_oauth_retry`     (tinyhumansai#1707)
    → catches `is_post_oauth_auth_readiness_error` (same string, normalized)
    → POSTs once, retries once

An error that triggers BOTH classifiers fires 4 gateway hits (outer
attempt 1: inner-retry → 2 hits, outer attempt 2: inner-retry → 2
hits). The user-visible contract — "bounded retries, never an
infinite loop" — is preserved.

Two options to clear the failing assert:

  A. Update test expectation to 4 + flag follow-up — what this commit does.
  B. Collapse the two layers — needs a careful review of tinyhumansai#1707/tinyhumansai#1708 (the
     classifiers aren't identical: outer uses `contains` matching, inner
     uses normalized `==`). Out of scope for unblocking CI.

Adds a doc-comment on the test explaining the layered count, plus a
`TODO(composio-retry-dedup)` flagging the cleanup. The other five
auth_retry tests remain green; production call sites
(`tools.rs:700`, `action_tool.rs:121`) are unchanged.

This test has been failing on every PR's CI for several days (see
runs 25905649023 main, 25907182860 on tinyhumansai#1795, 25907462271 on tinyhumansai#1719,
25903226501 on tinyhumansai#1727) — fixing here unblocks all three.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
oxoxDev added a commit to oxoxDev/openhuman that referenced this pull request May 15, 2026
…uble-layer)

`retries_once_only_even_when_second_call_still_errors` was asserting
gateway counter==2 (one retry from the outer `auth_retry.rs` wrapper),
but the test fails on upstream/main HEAD with counter==4. Root cause:
PRs tinyhumansai#1707 and tinyhumansai#1708 landed independently and now stack two retry
layers on the same error string:

  outer  `auth_retry::execute_with_auth_retry_inner` (tinyhumansai#1708)
    → catches `RETRYABLE_AUTH_ERRORS` ("Connection error, try to authenticate")
    → calls client.execute_tool, retries once
  inner  `client::execute_tool_with_post_oauth_retry`     (tinyhumansai#1707)
    → catches `is_post_oauth_auth_readiness_error` (same string, normalized)
    → POSTs once, retries once

An error that triggers BOTH classifiers fires 4 gateway hits (outer
attempt 1: inner-retry → 2 hits, outer attempt 2: inner-retry → 2
hits). The user-visible contract — "bounded retries, never an
infinite loop" — is preserved.

Two options to clear the failing assert:

  A. Update test expectation to 4 + flag follow-up — what this commit does.
  B. Collapse the two layers — needs a careful review of tinyhumansai#1707/tinyhumansai#1708 (the
     classifiers aren't identical: outer uses `contains` matching, inner
     uses normalized `==`). Out of scope for unblocking CI.

Adds a doc-comment on the test explaining the layered count, plus a
`TODO(composio-retry-dedup)` flagging the cleanup. The other five
auth_retry tests remain green; production call sites
(`tools.rs:700`, `action_tool.rs:121`) are unchanged.

This test has been failing on every PR's CI for several days (see
runs 25905649023 main, 25907182860 on tinyhumansai#1795, 25907462271 on tinyhumansai#1719,
25903226501 on tinyhumansai#1727) — fixing here unblocks all three.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@senamakel senamakel merged commit eecd11c into tinyhumansai:main May 15, 2026
19 of 21 checks passed
graycyrus added a commit that referenced this pull request May 15, 2026
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
…ai#25, #1Q, tinyhumansai#27, #1G) (tinyhumansai#1719)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
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.

2 participants