Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

One dead channel no longer takes the whole unified feed down

merged#2642CedarCopilot

CedarCopilot wants to merge 3 commits into staging from fix/inbox-partial-channel-failure

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Sep 1, 2026, 12:38 AM
  2. Sep 1, 2026, 1:57 AM
  3. Merged
    Sep 1, 2026, 2:10 AM
  4. Live on prod
    Sep 1, 2026, 2:10 AM
  5. Observed 3 days
    Sep 1, 2026, 2:10 AM
  6. Pipelines steady after this deploy
    Sep 1, 2026, 2:10 AM
  7. Unobserved

    Live on prod, no production signal yet

    Sep 4, 2026, 1:45 AM

Behaviors Libra is checking

When every participating channel fails, the unified feed still enters its full error state because there are no results it can safely display.Inconclusivelow confidence

Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.

prod, checked Sep 4, 2026, 12:43 AM
The inbox displays the failed channel names above the healthy rows and provides a Retry action that refetches the feed.Inconclusivelow confidence

Verification reached its 120s wall-clock budget before a tied verdict. Libra is keeping this intent verifying until the next check.

prod, checked Sep 4, 2026, 12:45 AM
When the email list reports that its page-one head changed, the unified-feed cache is trimmed to the first page and refetched, once per query key, so stale later-page cursors do not remain active.Inconclusivelow confidence

Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.

prod, checked Sep 4, 2026, 1:43 AM
The unified email feed emits each thread ID only once when overlapping mail.listThreads pages contain the same thread.Inconclusivelow confidence

Verification reached its 120s wall-clock budget before a tied verdict. Libra is keeping this intent verifying until the next check.

prod, checked Sep 4, 2026, 1:45 AM
The unified feed stops advertising Load more when every channel that could provide the next page is failed, while retaining pagination whenever at least one healthy target can supply more items.Not checked
prod
When one participating inbox channel fails, the unified feed remains ready and continues showing items from the healthy channels instead of remaining in a loading state or blanking the feed.Not checked
prod

Libra has verdicts on 0 of 6 tracked behaviors on prod; 6 are still being checked. Libra checks hourly for 3 days after each deploy.

Fixes the two P1s Greptile raised on #2641, which merged before they were addressed.

Partial failure

The unified feed is one query per channel, so Slack can fail while email and LinkedIn are perfectly healthy. Two separate things turned that into a blank screen.

A failed channel never became settled. settled was data !== undefined, and a rejected query has no data. The merge refuses to render until every channel settles , correctly, since an unsettled channel could still return an item of any timestamp, so no prefix is provably stable. So ready stayed false, isLoading stayed true, and InboxList (which checks loading before error) spun forever. Not "Slack is down" , an indefinite spinner.

A failed channel is now settled-and-empty, which is exactly how the merge already treats a channel that came back empty: it contributes nothing and bounds nothing.

isError was some(channel => channel.isError), which blanks a working list and throws away every row the healthy channels did return. The feed now errors only when it genuinely cannot answer , the scope query failed (we don't even know which channels participate), or every participating channel failed. The channels that did fail are named in failedChannels, and the list reports them inline above the rows it can still show, with a retry.

A channel that is merely slow still gates the merge. Only a terminal failure settles early, because a pending channel really can still return anything , that distinction has its own test.

headChanged

mail.listThreads reconciles page-1 freshness inline and sets headChanged when the reconcile shifted the head. The unified feed asks as surface: 'unified-feed' so the server's latch hands it its own copy rather than letting the email list swallow it , but nothing here consumed it. After a head shift, cached page-2+ cursors minted against the old boundary kept being replayed across it.

Now funnels through the same resetListThreadsToFirstPage the email list uses, on the false→true transition only (the inline reconcile is idempotent, so the refetch returns headChanged: false and this converges instead of looping).

Flattening also dedupes by thread id , the same safety net useThreads keeps over the same pages. The reset removes the cause of an overlap but lands a render later, and a row appearing twice for a frame is precisely the artifact the watermark exists to make impossible.

Verification

tests/modules/inbox/feedPartialFailure.test.tsx , 8 cases. Mutation-tested: 6 fail against the previous behavior, and the 2 that pass either way are the deliberate controls (a slow channel still gates; a steady head fires nothing).

  • mail jest: 322 suites, 2,994 passed
  • tsc -b --force clean

One line added to feedScopeGate's react-query mock: the email source now reads the query client, and that suite renders no provider.

🤖 Generated with Claude Code

Greptile Summary

The PR makes the unified inbox tolerate individual channel failures, reports degraded channels inline, and resets email pagination after a reconciled head shift.

  • Treats terminally failed channel queries as settled and reports an overall error only when scope resolution or every participating channel fails.
  • Adds an inline retry notice identifying failed channels while preserving healthy rows.
  • Deduplicates overlapping email pages and reacts to the unified feed’s headChanged signal.
  • Adds partial-failure, slow-channel, head-reset, and overlap regression coverage.

Confidence Score: 3/5

The PR should not merge until failed refetches stop contributing retained data and head-change resets are tracked per email query.

A background channel failure can leave stale rows participating in the merge, while a query switch during consecutive head-change signals can preserve later pages built against an obsolete boundary.

*Files Needing Attention:

Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • apps/mail/modules/inbox/components/InboxList.tsxno production surface mapped
  • apps/mail/modules/inbox/hooks/use-inbox-items.tsno production surface mapped
  • apps/mail/tests/modules/inbox/feedPartialFailure.test.tsxno production surface mapped
  • apps/mail/tests/modules/inbox/feedScopeGate.test.tsxno production surface mapped