Skip to main content
← Back to list
01Issue
BugOpenSwamp Club
AssigneesNone

Relationships

#1589 A ledger read failure still renders as 'no activity': loadProfileScore swallows to an empty standing behind HTTP 200

Opened by keeb · 8/11/2026

Split out of #1586 (fixed in PR #1056), which asked for this as its request 1 and which deliberately deferred it.

What is wrong

loadProfileScore (lib/app/profile-score-reads.ts) ends with .catch(() => EMPTY_PROFILE_SCORE). Any ClickHouse ledger failure — timeout, breaker open, CLICKHOUSE_URL unset — therefore becomes an empty standing that is indistinguishable from an operative who has done nothing, and GET /api/v1/users/<name>/score-history serves it as HTTP 200 with count: 0.

That is exactly how #1586 hid for months: the endpoint burned its full 5s ceiling and answered 200 with zero entries, so it read as 'this operative has no activity' rather than 'this read failed'. The expensive read that made it fire constantly is gone, so the frequency is now low — the misreporting is not.

What #1056 already did

Only the tracing half. The span used to stamp Outcomes.success even when the catch fired; it now reports upstream_error with a score_reads.degraded attribute, so a degraded read is at least visible in Axiom. Nothing changed for a viewer or an API consumer.

What is still needed

Distinguish degraded from empty on the wire and on the page, the same way the leaderboard fill had to check pending before the empty-rows branch:

  1. The API should not answer 200 with a silently empty body when the ledger read failed. Either a 503 (matching the board endpoint's shape, which the island already retries on) or an explicit marker on the payload.
  2. The profile / activity page should say the score section is temporarily unavailable rather than rendering a legitimate-looking empty state. Note the deliberate asymmetry to preserve: the swallow exists so a ledger outage does not take down bio, badges, collectives, lab and extensions — that part is correct and should stay.
  3. loadHeadlineTotal has the same .catch(() => null) shape and should be considered in the same pass.

Worth deciding

Whether an empty standing and a failed read should be different types at the lib/app boundary rather than the same value, so a caller cannot accidentally treat one as the other — the current shape makes the bug easy to reintroduce anywhere else that consumes these helpers.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

8/11/2026, 1:31:01 AM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.