#2511·manifest

Requests chart: grouping by provider/harness drops failed requests, totals diverge from By request status

Author: SebConejoCreated Jul 15, 2026Updated Jul 15, 2026
Labelsbugseverity: medium

What happened

On the Overview Requests chart, switching the grouping changes the total volume. Real example at one bucket (11:00): "By provider" shows 5 OpenAI + 2 Anthropic + 1 Gemini = 8 requests. "By request status" on the same bucket shows 6 success + 2 healed via fallback + 4 error = 12 requests. The 4 failed requests exist in the status view and vanish in the provider view.

Why

The provider/harness series reuse sqlCountMessages() (query-helpers.ts:58): COUNT(DISTINCT COALESCE(request_id, id)) FILTER (WHERE status NOT IN ('error','fallback_error','rate_limited','auto_fixed')). That counter was designed for usage metrics (served messages, billing-adjacent), where excluding failures makes sense. Used in a volume chart it silently filters errors: a request whose every attempt failed is attributed to no provider and disappears. The header "Requests" KPI (aggregation.service.ts:111) shares the same counter, so it matches provider/harness views but not the status view.

Expected behavior

Grouping is a lens, not a filter. The three views of the same chart (By request status / By provider / By harness) must stack to the same total, equal to the header Requests KPI. A request with only failed attempts still counts, attributed to the provider (or harness) of its terminal attempt: the one whose error the caller received. One request, one series, counted once, success or failure.

Proposed approach

Split the two notions instead of bending one counter:

  1. Keep sqlCountMessages() as is for usage/billing surfaces (connection tables 30d, tokens, cost, per-model usage). "Served requests" is the right question there.
  2. Introduce a request-volume counter for the Overview chart series and the header KPI: count every request once, attribute to the terminal attempt's provider/harness. Terminal attempt = the last attempt of the request (the success, or the last failure when none succeeded).
  3. Frontend untouched: same chart components, same series shapes.

Acceptance criteria

  • For any bucket and range, sum(By provider) == sum(By harness) == sum(By request status) == Requests KPI
  • A request whose attempts all failed appears in the provider view, under the terminal attempt's provider
  • A fallback-rescued request still counts once, under the succeeding provider
  • Usage/billing surfaces (connections 30d tables, tokens, cost) keep their current served-only semantics

Environment

Local (dev worktree, branch fluoridated-beetle / PR #2485), macOS Apple Silicon, seeded + live OpenClaw traffic.

cc @guillaumegay13