Skip to content

fix(web): answer a failed profile, community or wave lookup as retryable, not 404 - #2001

Merged
feruzm merged 3 commits into
developfrom
fix/ssr-lookup-failure-not-404
Oct 30, 2026
Merged

feruzm merged 3 commits into
developfrom
fix/ssr-lookup-failure-not-404

Conversation

@feruzm

@feruzm feruzm commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Closes #2000

What changed

A server lookup of an account, community or wave post that failed upstream (RPC error, rate limit, timeout) was answered as "does not exist": the page sent a 404 for a live profile. Now a failed lookup is told apart from a missing record.

  • prefetchRequired (core/react-query/query-helpers.ts), next to prefetchQuery and on the same React Query fetch (a layout and page asking for one key still share one upstream call):
    • record present: returned;
    • lookup succeeded and found nothing, or a node answered with a not-found assert: notFound() (unchanged 404, still cacheable);
    • otherwise (error, timeout): marks the response lookup-unavailable and throws SsrLookupUnavailableError.
  • Pages: profile index, [section] (both branches), followers, following, trail, communities, permissions, settings, referrals, insights; community index, [tag] (both branches), activities, subscribers, roles; the wave page. [section] now answers an unknown section before any lookup.
  • ssr-degraded.js: a lookup-unavailable response that ended in a server error is sent as 503 with Retry-After: 30, still private, no-store. A 404 or 200 keeps its status. Headers passed to writeHead can no longer override a degraded response's Cache-Control. The per-minute log line ends with a short sample of the failing errors (with the node when the SDK names it, control characters removed). A failed record lookup is counted under its own reason even when a layout marked the request first.
  • Error module for the profile, community and wave segments (features/shared/lookup-unavailable): a retry (refresh + reset) inside the layout instead of the global 500. It renders on client-side navigation and on the profile sub-pages that stream through their own loading.tsx; on routes without a Suspense boundary a full page load gets the 503 instead. Client render errors are still reported, through the lazy Sentry facade (no SDK on the eager bundle). It catches any error in those routes, so its copy names no cause.
  • Sentry (server): SsrLookupUnavailableError is dropped in beforeSend; it is an expected upstream condition, counted in the log line.
  • isHiveNotFoundError: an account name no account can have (too long, or invalid characters, as answered by both the chain and hivemind) is a not-found answer, so such URLs stay cacheable 404s.

Behaviour by layer

Case Before After
Healthy lookup 200, tier caching unchanged
Missing account/community/wave 404 unchanged
Failed lookup, profile index / feed tabs / community / wave 404 (no-store) 503 + Retry-After, no-store; an origin with a fresh cached copy can serve it stale while an edge retry can try another origin
Failed lookup, profile sub-pages with loading.tsx 200 + not-found UI 200 + retry UI (no-store)
Failed lookup on client navigation not-found UI retry UI inside the layout

Known residual (same class as before): a client-navigation (RSC) response writes its head before the page resolves, so a failure there is a "late" mark and keeps the route's Cache-Control, as a not-found did before. The retry sends a different router state, so it does not hit that entry.

Post pages (/@author/permlink) are unchanged: a failed lookup there already renders the client-side fallback that re-reads the chain from the browser.

Test first

Command for each: pnpm --filter @ecency/web exec vitest run <spec> (paths relative to apps/web).

Spec Failed before the change on After
src/specs/core/react-query/prefetch-required.spec.ts promise resolved "undefined" instead of rejecting (failed and timed-out lookups), promise resolved "null" instead of rejecting (missing) 8 passed
src/specs/app/profile-community-lookup-unavailable.spec.ts expected Error: NEXT_NOT_FOUND to be an instance of SsrLookupUnavailableError on all 17 profile/community failed-lookup cases, then the wave case 37 passed (missing-record cases pin the existing 404)
src/specs/ssr-degraded.spec.ts expected 500 to be 503 (3 cases), log line without the error sample, expected 'public, s-maxage=300' to be 'private, no-store' 22 passed
src/specs/app/lookup-unavailable-error-boundary.spec.tsx Unable to find an element with the text: lookup-unavailable.title, Sentry not called for a client error, segment error.tsx missing 7 passed
src/specs/utils/sentry-config-wiring.spec.ts expected { …(4) } to be null 4 passed
src/specs/utils/hive-not-found-error.spec.ts expected false to be true for the impossible account name asserts 17 passed

Mutations (each reverted after): prefetchRequired returning undefined on failure (20 failures), every error state answered notFound() (19), timeout answered notFound() (1), 503 rewrite removed (3), one page reverted to prefetchQuery + notFound() (1), Sentry filter removed (1), header strip removed (1).

Full web suite: 584 files, 5979 tests passed. tsc --noEmit clean, eslint clean on changed files, icon/slim-entries/origin-config audits pass.

Also verified with next dev, the production preload and a stub RPC node: failed lookup on the profile index and a community feed answered 503 + Retry-After: 30 + private, no-store; a missing account answered 404; a healthy lookup answered 200 with unchanged headers.

The three render specs that stub prefetchQuery now stub prefetchRequired the same way. The global SDK spec mock gained the profile and ranked feed builders the pages build their queries with.

…ble, not 404

A server lookup of an account, community or wave post that failed upstream
(RPC error, rate limit, timeout) was treated as "does not exist": the page
answered 404, telling readers and crawlers a live page was gone.

- prefetchRequired: returns the record, calls notFound() only when the lookup
  succeeded and found nothing (or a node asserted it does not exist). It
  otherwise marks the response lookup-unavailable and throws
  SsrLookupUnavailableError. Same React Query fetch as prefetchQuery.
- Profile, community and wave pages use it for their record lookup. The
  profile section page answers an unknown section before any lookup.
- ssr-degraded.js sends a lookup-unavailable response that ended in a server
  error as 503 with Retry-After (still private, no-store). Headers handed
  to writeHead can no longer override a degraded response's Cache-Control.
  The minute log line carries a short sample of the failing errors.
- Profile, community and wave segments get an error module with a retry
  inside the layout instead of the global 500. Client render errors are
  still reported, through the lazy Sentry facade.
- The server Sentry config drops SsrLookupUnavailableError.
- isHiveNotFoundError also recognises the asserts for an account name no
  account can have, so those URLs stay cacheable 404s.

Closes #2000
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T19:43:48.801099Z 82d7b9c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Treat failed profile, community, and wave lookups as retryable

🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Distinguishes missing records from failed lookups, preserving 404s while making upstream failures
 retryable.
• Sends unstreamed lookup failures as no-store 503s and offers an in-layout retry after navigation.
• Adds route, response, and boundary tests while keeping expected upstream failures out of Sentry.
Diagram

graph TD
  Routes["Profile, community, wave"] --> Required["Required lookup"] --> Outcome{"Lookup outcome"}
  Outcome -->|found| Healthy["Render page"]
  Outcome -->|absent| Missing["Return 404"]
  Outcome -->|failed| Failed["Mark and throw"] --> Preload["No-store 503"]
  Failed -->|client or streamed| Boundary["In-layout retry"]
Loading
High-Level Assessment

Keep the shared required-lookup helper and extend the existing degraded-response preload. They preserve React Query request sharing and centralize the missing-versus-failed distinction; page-local handling would duplicate it, while replacing these pages with route handlers solely to control status codes would substantially broaden the change.

Files changed (37) +870 / -145

Enhancement (2) +6 / -0
en-US.jsonAdd lookup-unavailable retry copy +5/-0

Add lookup-unavailable retry copy

• Adds English title, explanation, and retry-button text for the segment error boundary.

apps/web/src/features/i18n/locales/en-US.json

index.tsExport the shared lookup error boundary +1/-0

Export the shared lookup error boundary

• Provides a shared import for the profile, community, and wave segment error modules.

apps/web/src/features/shared/lookup-unavailable/index.ts

Bug fix (24) +307 / -124
sentry.server.config.tsExclude expected lookup failures from server Sentry +6/-0

Exclude expected lookup failures from server Sentry

• Drops SsrLookupUnavailableError events in beforeSend. These upstream failures are instead counted by degraded-response logging.

apps/web/sentry.server.config.ts

page.tsxRequire community records in feed and archive branches +6/-7

Require community records in feed and archive branches

• Uses prefetchRequired for both community lookup paths, preserving 404s for missing communities while throwing on failed lookups.

apps/web/src/app/(dynamicPages)/community/[community]/[tag]/page.tsx

page.tsxDistinguish failed community lookups on activities +2/-6

Distinguish failed community lookups on activities

• Replaces the nullable prefetch and local not-found check with a required community lookup.

apps/web/src/app/(dynamicPages)/community/[community]/activities/page.tsx

error.tsxAdd the community segment error module +5/-0

Add the community segment error module

• Exports the shared retry boundary for community errors that reach the client.

apps/web/src/app/(dynamicPages)/community/[community]/error.tsx

page.tsxRequire the community before rendering its feed +4/-6

Require the community before rendering its feed

• Uses prefetchRequired so a failed community lookup no longer becomes a missing-community 404.

apps/web/src/app/(dynamicPages)/community/[community]/page.tsx

page.tsxRequire the community on the roles page +2/-6

Require the community on the roles page

• Replaces the nullable prefetch and local not-found check with a required lookup.

apps/web/src/app/(dynamicPages)/community/[community]/roles/page.tsx

page.tsxRequire the community on the subscribers page +2/-6

Require the community on the subscribers page

• Uses prefetchRequired to separate missing communities from upstream lookup failures.

apps/web/src/app/(dynamicPages)/community/[community]/subscribers/page.tsx

page.tsxValidate feed sections before requiring the account +20/-11

Validate feed sections before requiring the account

• Returns 404 for unknown sections without calling upstream. Both normal and cursor feed branches now use prefetchRequired for the account.

apps/web/src/app/(dynamicPages)/profile/[username]/[section]/page.tsx

page.tsxRequire the account on profile communities +2/-8

Require the account on profile communities

• Uses prefetchRequired before rendering instead of treating any absent prefetch result as a 404.

apps/web/src/app/(dynamicPages)/profile/[username]/communities/page.tsx

error.tsxAdd the profile segment error module +5/-0

Add the profile segment error module

• Exports the shared in-layout retry boundary for profile errors that reach the client.

apps/web/src/app/(dynamicPages)/profile/[username]/error.tsx

page.tsxRequire the account on followers +2/-7

Require the account on followers

• Replaces the nullable account prefetch and not-found check with prefetchRequired.

apps/web/src/app/(dynamicPages)/profile/[username]/followers/page.tsx

page.tsxRequire the account on following +2/-7

Require the account on following

• Replaces the nullable account prefetch and not-found check with prefetchRequired.

apps/web/src/app/(dynamicPages)/profile/[username]/following/page.tsx

page.tsxRequire the account before showing insights +2/-7

Require the account before showing insights

• Uses prefetchRequired for the account while leaving the separate Pro-membership prefetch unchanged.

apps/web/src/app/(dynamicPages)/profile/[username]/insights/page.tsx

page.tsxDistinguish unavailable accounts on the profile index +10/-8

Distinguish unavailable accounts on the profile index

• Requires the account through prefetchRequired rather than interpreting a failed prefetch as not found. A returned account can be used without a nullable guard.

apps/web/src/app/(dynamicPages)/profile/[username]/page.tsx

page.tsxRequire the account before permissions checks +3/-7

Require the account before permissions checks

• Uses prefetchRequired while retaining the existing redirect for viewers other than the account owner.

apps/web/src/app/(dynamicPages)/profile/[username]/permissions/page.tsx

page.tsxRequire the account on referrals +2/-7

Require the account on referrals

• Replaces the nullable prefetch and local 404 check with prefetchRequired.

apps/web/src/app/(dynamicPages)/profile/[username]/referrals/page.tsx

page.tsxRequire the account before settings checks +3/-7

Require the account before settings checks

• Uses prefetchRequired while preserving the non-owner redirect.

apps/web/src/app/(dynamicPages)/profile/[username]/settings/page.tsx

page.tsxRequire the account on the trail page +2/-7

Require the account on the trail page

• Uses prefetchRequired so an upstream failure is no longer reported as a missing profile.

apps/web/src/app/(dynamicPages)/profile/[username]/trail/page.tsx

error.tsxAdd the wave segment error module +5/-0

Add the wave segment error module

• Exports the shared retry boundary for wave lookup errors that reach the client.

apps/web/src/app/waves/[author]/[permlink]/error.tsx

page.tsxRequire the wave record before rendering +6/-6

Require the wave record before rendering

• Uses prefetchRequired for the wave post, retaining a 404 for genuine absence without assigning that status to upstream failures.

apps/web/src/app/waves/[author]/[permlink]/page.tsx

query-helpers.tsClassify required-record lookup outcomes +73/-4

Classify required-record lookup outcomes

• Adds prefetchRequired on the existing React Query prefetch, returning available data, calling notFound for confirmed absence, and marking and throwing SsrLookupUnavailableError for errors or timeouts. Degraded marks can now include a failure detail.

apps/web/src/core/react-query/query-helpers.ts

lookup-unavailable-error.tsxOffer an in-layout retry for lookup failures +63/-0

Offer an in-layout retry for lookup failures

• Adds a client error boundary UI that refreshes the route and resets the segment. It reports client-render errors through the lazy Sentry facade but does not re-report server-render errors.

apps/web/src/features/shared/lookup-unavailable/lookup-unavailable-error.tsx

hive-not-found-error.tsRecognize invalid account names as not found +7/-1

Recognize invalid account names as not found

• Classifies oversized names and invalid account-character or length assertions as genuine not-found responses rather than upstream failures.

apps/web/src/utils/hive-not-found-error.ts

ssr-degraded.jsSend unstreamed lookup failures as no-store 503s +73/-6

Send unstreamed lookup failures as no-store 503s

• Tracks lookup-unavailable marks and rewrites resulting server errors to 503 with Retry-After, without changing successful or not-found statuses. Protects degraded Cache-Control against writeHead overrides and adds bounded failure details to periodic logs.

apps/web/ssr-degraded.js

Tests (10) +543 / -20
lookup-unavailable-error-boundary.spec.tsxTest retry boundaries and reporting behavior +79/-0

Test retry boundaries and reporting behavior

• Checks refresh and reset, client-only Sentry reporting, placement in all three segments, and use of the lazy Sentry facade.

apps/web/src/specs/app/lookup-unavailable-error-boundary.spec.tsx

profile-community-lookup-unavailable.spec.tsTest required lookups across affected routes +178/-0

Test required lookups across affected routes

• Exercises failed and missing account or community lookups across profile and community page branches. Also checks failed wave lookups and node not-found assertions.

apps/web/src/specs/app/profile-community-lookup-unavailable.spec.ts

profile-page-ssr-stream.spec.tsxAdapt profile streaming fixtures to required lookups +13/-7

Adapt profile streaming fixtures to required lookups

• Makes the existing successful account fixture serve both prefetchQuery and prefetchRequired.

apps/web/src/specs/app/profile-page-ssr-stream.spec.tsx

profile-section-page-ssr-stream.spec.tsxAdapt section streaming fixtures to required lookups +13/-7

Adapt section streaming fixtures to required lookups

• Uses one fixture for both prefetch helpers so the section streaming test retains its successful account lookup.

apps/web/src/specs/app/profile-section-page-ssr-stream.spec.tsx

wave-page-ssr-stream.spec.tsxAdapt wave streaming fixtures to required lookups +8/-4

Adapt wave streaming fixtures to required lookups

• Stubs prefetchRequired with the existing wave-entry fixture alongside prefetchQuery.

apps/web/src/specs/app/wave-page-ssr-stream.spec.tsx

prefetch-required.spec.tsTest required-prefetch result classification +120/-0

Test required-prefetch result classification

• Uses a real QueryClient to check found, missing, asserted-not-found, errored, cached-data, and timed-out outcomes. Verifies failure marks include useful details.

apps/web/src/specs/core/react-query/prefetch-required.spec.ts

setup-any-spec.tsExtend shared SDK query-builder mocks +22/-0

Extend shared SDK query-builder mocks

• Adds account-post and ranked-feed query builders needed when route tests construct their feed and archive queries.

apps/web/src/specs/setup-any-spec.ts

ssr-degraded.spec.tsTest retryable degraded HTTP responses +90/-1

Test retryable degraded HTTP responses

• Covers 503 rewriting, Retry-After, no-store headers even with writeHead overrides, unchanged 200 and 404 statuses, and bounded error samples in logs.

apps/web/src/specs/ssr-degraded.spec.ts

hive-not-found-error.spec.tsTest impossible account-name assertions +6/-1

Test impossible account-name assertions

• Adds chain and Hivemind assertions for account names that cannot exist to the not-found cases.

apps/web/src/specs/utils/hive-not-found-error.spec.ts

sentry-config-wiring.spec.tsTest server Sentry lookup-error filtering +14/-0

Test server Sentry lookup-error filtering

• Verifies that beforeSend drops SsrLookupUnavailableError while retaining unrelated errors.

apps/web/src/specs/utils/sentry-config-wiring.spec.ts

Documentation (1) +14 / -1
global-error.tsxDocument the segment-specific error boundaries +14/-1

Document the segment-specific error boundaries

• Explains why profile, community, and wave now have retry boundaries and when a full page load instead receives a server response.

apps/web/src/app/global-error.tsx

@greptile-apps

greptile-apps Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The PR changes failure responses across profile, community, and wave page lookups.

The PR still needs the existing wave lookup fix before merging.

Fix All in Claude CodeFindings

  1. P1 Existing waves still return 404 ▶

Summary

Failed required lookups now get retryable errors rather than false 404s.

  • Profile, community, and wave pages tell missing records from failed lookups.
  • Profile, community, and wave routes keep a retry panel inside their layout.
  • Failed server lookups return a retryable 503 and add useful log details.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Required lookup] --> B{Query result}
  B -->|Record present| C[Render page]
  B -->|Successful empty result or not-found error| D[404]
  B -->|Error or timeout| E[Mark lookup-unavailable and throw]
  E --> F{Headers already sent?}
  F -->|No, response is a server error| G[503, Retry-After, no-store]
  F -->|Yes| H[Keep status and show retry screen]
Loading

Reviews (2) · Last reviewed commit: "fix(web): skip empty log samples and cut..." · Reviewed by Greptile

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (2) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. New lookup tests bypass type checking 📘 Rule violation ⚙ Maintainability
Description
The new lookup regression spec uses as any, Promise, and arrays containing Promise route
loaders instead of explicit types. These casts and annotations suppress checking across mocked query
options, route imports, and render helpers in the added test paths.
Code

apps/web/src/specs/app/profile-community-lookup-unavailable.spec.ts[R66-70]

+      ({ queryKey: ["accounts", "full", username], queryFn: lookup(accountOutcome) }) as any
+  );
+  vi.mocked(getCommunityQueryOptions).mockImplementation(
+    (name?: string) =>
+      ({ queryKey: ["communities", "single", name], queryFn: lookup(communityOutcome) }) as any
Evidence
The added TypeScript test code introduces explicit any assertions and generic types in multiple
changed sections.

Rule 2668119: Disallow implicit and any types in new TypeScript code
apps/web/src/specs/app/profile-community-lookup-unavailable.spec.ts[66-70]
apps/web/src/specs/app/profile-community-lookup-unavailable.spec.ts[82-105]
apps/web/src/specs/app/profile-community-lookup-unavailable.spec.ts[147-152]


2. Lookup tests hardcode React Query keys 📘 Rule violation ⚙ Maintainability
Description
The added query-option mocks construct keys with array literals such as `['accounts', 'full',
username] and ['entry', author, permlink] rather than obtaining them from shared QueryKeys`
constants. These keys are passed through the mocked query builders and consumed by the real query
client in the regression tests, so the new query paths bypass the repository's centralized key
definitions.
Code

apps/web/src/specs/app/profile-community-lookup-unavailable.spec.ts[R64-70]

+  vi.mocked(getAccountFullQueryOptions).mockImplementation(
+    (username?: string) =>
+      ({ queryKey: ["accounts", "full", username], queryFn: lookup(accountOutcome) }) as any
+  );
+  vi.mocked(getCommunityQueryOptions).mockImplementation(
+    (name?: string) =>
+      ({ queryKey: ["communities", "single", name], queryFn: lookup(communityOutcome) }) as any
Evidence
The changed test code defines React Query option objects with manually written array-literal query
keys in the account, community, and wave mocks.

Rule 2667922: Use QueryKeys constants for react-query keys instead of hardcoded literals
apps/web/src/specs/app/profile-community-lookup-unavailable.spec.ts[64-70]
apps/web/src/specs/app/profile-community-lookup-unavailable.spec.ts[149-153]
apps/web/src/specs/setup-any-spec.ts[96-115]


3. Upstream errors can alter terminal logs ✓ Resolved
Description
sampleDetail() replaces whitespace in the lookup error message but leaves terminal-control
characters intact before writing the sample to stderr. A node-supplied RPC error message can
therefore carry escape sequences into the per-minute log when a required lookup fails.
Code

apps/web/ssr-degraded.js[68]

+  details.add(String(detail).replace(/\s+/g, " ").slice(0, MAX_DETAIL_LENGTH));
Evidence
The SDK constructs RPCError.message from the RPC response. The new lookup helper copies that
message into the degradation detail, and the preload's whitespace-only normalization permits
non-whitespace control characters to reach stderr.

packages/sdk/src/hive-tx/helpers/call.ts[160-170]
packages/sdk/src/hive-tx/helpers/call.ts[1138-1148]
apps/web/src/core/react-query/query-helpers.ts[155-163]
apps/web/src/core/react-query/query-helpers.ts[197-203]
apps/web/ssr-degraded.js[63-69]
apps/web/ssr-degraded.js[113-119]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Node-supplied lookup errors can contain terminal-control characters that the new stderr error sample preserves.
## Fix Focus Areas
- apps/web/ssr-degraded.js[66-69]
- apps/web/ssr-degraded.js[113-119]
## Recommended Fix
Escape or remove control characters, including ANSI escape sequences, before retaining error details for the log. Keep the sample bounded and test a node error containing an escape sequence.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (1)
4. Lookup failures disappear from reason counts 🐞 Bug ◔ Observability
Description
mark() sets ctx.unavailable for a required-lookup failure but leaves an earlier ctx.reason
unchanged. If an optional prefetch has already marked the request, the resulting 503 is counted
under that earlier reason; after headers are sent, the existing reason also prevents a late
lookup-failure count.
Code

apps/web/ssr-degraded.js[R89-91]

+  // Kept apart from ctx.reason: an earlier mark under another reason must not
+  // stop a failed record lookup from going out as a 503.
+  if (why === LOOKUP_UNAVAILABLE && !ctx.res.headersSent) ctx.unavailable = true;
Evidence
The helper emits a lookup-unavailable mark before throwing, but the preload retains the first
reason for sent responses and records a late reason only when no earlier reason exists. Its separate
unavailable flag still rewrites the response to 503.

apps/web/src/core/react-query/query-helpers.ts[197-203]
apps/web/ssr-degraded.js[84-101]
apps/web/ssr-degraded.js[157-180]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A required-lookup failure can produce a 503 without appearing in the per-reason lookup-unavailable counts when another prefetch marked the request first.
## Fix Focus Areas
- apps/web/ssr-degraded.js[84-101]
- apps/web/ssr-degraded.js[157-180]
## Recommended Fix
Track the lookup-failure reason independently of the first degradation reason, and include it in the appropriate sent or late count without counting the response twice. Add coverage for an earlier prefetch mark followed by a required-lookup failure.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

5. Any profile/community page bug shows a 'try again' message ✓ Resolved
Description
LookupUnavailableError is mounted as the segment error.tsx for profile/[username],
community/[community] and the wave route, so it catches every error thrown in those subtrees
(wallet, points, insights, client render bugs), not only SsrLookupUnavailableError. Each one is
shown with the fixed text "The data it needs did not arrive in time. It is usually back in a
moment." and a retry button. A deterministic bug therefore looks like a temporary upstream problem
to the reader, and retrying never fixes it.
Code

apps/web/src/features/shared/lookup-unavailable/lookup-unavailable-error.tsx[R54-60]

+  return (
+    <div className="flex flex-col items-start gap-4 p-6 rounded-xl border border-[--border-color] bg-white dark:bg-dark-200">
+      <h2 className="text-lg font-semibold">{i18next.t("lookup-unavailable.title")}</h2>
+      <p className="text-gray-600 dark:text-gray-400">
+        {i18next.t("lookup-unavailable.description")}
+      </p>
+      <Button onClick={retry}>{i18next.t("lookup-unavailable.retry")}</Button>
Evidence
The boundary always renders the lookup-unavailable strings, whatever the error is. It is exported as
the default error module for all three segments (profile/[username]/error.tsx,
community/[community]/error.tsx, waves/[author]/[permlink]/error.tsx), and Next error boundaries
catch every non-navigation error from nested segments.

apps/web/src/features/shared/lookup-unavailable/lookup-unavailable-error.tsx[39-63]
apps/web/src/app/(dynamicPages)/profile/[username]/layout.tsx[18-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The segment error boundary shows the 'data did not arrive in time' message for every error in the profile, community and wave subtrees, including real client render bugs.
## Fix Focus Areas
- apps/web/src/features/shared/lookup-unavailable/lookup-unavailable-error.tsx[39-63]
## Recommended Fix
When `error.digest` is absent (a client render error) or `error.name !== "SsrLookupUnavailableError"` on the client, show a generic error message (e.g. reuse the `global-error.description` string) and keep the lookup-unavailable copy for server-render errors, which carry a digest. Keep the retry button in both cases.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can route each severity your way: inline, summary, both, or drop

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +66 to +70
({ queryKey: ["accounts", "full", username], queryFn: lookup(accountOutcome) }) as any
);
vi.mocked(getCommunityQueryOptions).mockImplementation(
(name?: string) =>
({ queryKey: ["communities", "single", name], queryFn: lookup(communityOutcome) }) as any

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. New lookup tests bypass type checking 📘 Rule violation ⚙ Maintainability

The new lookup regression spec uses as any, Promise<any>, and arrays containing Promise<any>
route loaders instead of explicit types. These casts and annotations suppress checking across mocked
query options, route imports, and render helpers in the added test paths.

Comment on lines +64 to +70
vi.mocked(getAccountFullQueryOptions).mockImplementation(
(username?: string) =>
({ queryKey: ["accounts", "full", username], queryFn: lookup(accountOutcome) }) as any
);
vi.mocked(getCommunityQueryOptions).mockImplementation(
(name?: string) =>
({ queryKey: ["communities", "single", name], queryFn: lookup(communityOutcome) }) as any

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Lookup tests hardcode react query keys 📘 Rule violation ⚙ Maintainability

The added query-option mocks construct keys with array literals such as `['accounts', 'full',
username] and ['entry', author, permlink] rather than obtaining them from shared QueryKeys`
constants. These keys are passed through the mocked query builders and consumed by the real query
client in the regression tests, so the new query paths bypass the repository's centralized key
definitions.

Comment thread apps/web/ssr-degraded.js Resolved
Comment thread apps/web/ssr-degraded.js Outdated
Comment on lines +89 to +91
// Kept apart from ctx.reason: an earlier mark under another reason must not
// stop a failed record lookup from going out as a 503.
if (why === LOOKUP_UNAVAILABLE && !ctx.res.headersSent) ctx.unavailable = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

4. Lookup failures disappear from reason counts 🐞 Bug ◔ Observability

mark() sets ctx.unavailable for a required-lookup failure but leaves an earlier ctx.reason
unchanged. If an optional prefetch has already marked the request, the resulting 503 is counted
under that earlier reason; after headers are sent, the existing reason also prevents a late
lookup-failure count.
Agent Prompt
## Issue description
A required-lookup failure can produce a 503 without appearing in the per-reason lookup-unavailable counts when another prefetch marked the request first.
## Fix Focus Areas
- apps/web/ssr-degraded.js[84-101]
- apps/web/ssr-degraded.js[157-180]
## Recommended Fix
Track the lookup-failure reason independently of the first degradation reason, and include it in the appropriate sent or late count without counting the response twice. Add coverage for an earlier prefetch mark followed by a required-lookup failure.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +49 to +51
prefetchRequired(
EcencyEntriesCacheManagement.getEntryQueryByPath(authorName, permlink)
) as Promise<WaveEntry>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Existing waves still return 404

If the first node returns null for an existing wave, getPostQueryOptions checks other nodes through verifyPostOnAlternateNode. That helper catches failed checks and returns null. The new prefetchRequired call then sees a successful empty result and calls notFound(), sending a cacheable 404 instead of a retryable error.

This leaves part of the intended fix incomplete. Preserve failed checks through the SDK helper so this case reaches SsrLookupUnavailableError.

Fix in Claude Code

@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 35 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d9a7ec34-9607-4eeb-a658-6344c5b21184
Commits

Reviewing files that changed from the base of the PR and between 82d7b9c and 2465b3f.

Files selected for processing: 4
  • apps/web/src/features/i18n/locales/en-US.json
  • apps/web/src/features/shared/lookup-unavailable/lookup-unavailable-error.tsx
  • apps/web/src/specs/ssr-degraded.spec.ts
  • apps/web/ssr-degraded.js

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7a2acd81-6703-44c9-b64b-1a1095366f77
Commits

Reviewing files that changed from the base of the PR and between fb95089 and 82d7b9c.

Files selected for processing: 37
  • apps/web/sentry.server.config.ts
  • apps/web/src/app/(dynamicPages)/community/[community]/[tag]/page.tsx
  • apps/web/src/app/(dynamicPages)/community/[community]/activities/page.tsx
  • apps/web/src/app/(dynamicPages)/community/[community]/error.tsx
  • apps/web/src/app/(dynamicPages)/community/[community]/page.tsx
  • apps/web/src/app/(dynamicPages)/community/[community]/roles/page.tsx
  • apps/web/src/app/(dynamicPages)/community/[community]/subscribers/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/[section]/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/communities/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/error.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/followers/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/following/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/insights/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/permissions/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/referrals/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/settings/page.tsx
  • apps/web/src/app/(dynamicPages)/profile/[username]/trail/page.tsx
  • apps/web/src/app/global-error.tsx
  • apps/web/src/app/waves/[author]/[permlink]/error.tsx
  • apps/web/src/app/waves/[author]/[permlink]/page.tsx
  • apps/web/src/core/react-query/query-helpers.ts
  • apps/web/src/features/i18n/locales/en-US.json
  • apps/web/src/features/shared/lookup-unavailable/index.ts
  • apps/web/src/features/shared/lookup-unavailable/lookup-unavailable-error.tsx
  • apps/web/src/specs/app/lookup-unavailable-error-boundary.spec.tsx
  • apps/web/src/specs/app/profile-community-lookup-unavailable.spec.ts
  • apps/web/src/specs/app/profile-page-ssr-stream.spec.tsx
  • apps/web/src/specs/app/profile-section-page-ssr-stream.spec.tsx
  • apps/web/src/specs/app/wave-page-ssr-stream.spec.tsx
  • apps/web/src/specs/core/react-query/prefetch-required.spec.ts
  • apps/web/src/specs/setup-any-spec.ts
  • apps/web/src/specs/ssr-degraded.spec.ts
  • apps/web/src/specs/utils/hive-not-found-error.spec.ts
  • apps/web/src/specs/utils/sentry-config-wiring.spec.ts
  • apps/web/src/utils/hive-not-found-error.ts
  • apps/web/ssr-degraded.js

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

Required profile, community, and wave lookups now distinguish missing records from lookup failures. Lookup failures raise a typed error, receive SSR 503 handling, and use retry error boundaries during client navigation.

Changes: Required lookup availability

Layer / File(s) Summary
Required lookup classification
apps/web/src/core/react-query/query-helpers.ts, apps/web/src/utils/hive-not-found-error.ts, apps/web/src/specs/core/react-query/prefetch-required.spec.ts, apps/web/src/specs/utils/hive-not-found-error.spec.ts
prefetchRequired returns available data, calls notFound() for successful empty results or recognized Hive not-found errors, and marks other failures before throwing SsrLookupUnavailableError. Hive not-found matching now includes additional invalid account-name responses.
Profile, community, and wave page integration
apps/web/src/app/(dynamicPages)/profile/..., apps/web/src/app/(dynamicPages)/community/..., apps/web/src/app/waves/[author]/[permlink]/page.tsx, apps/web/src/specs/app/profile-community-lookup-unavailable.spec.ts, apps/web/src/specs/app/*-ssr-stream.spec.tsx, apps/web/src/specs/setup-any-spec.ts
Required account, community, and wave lookups use prefetchRequired instead of nullable prefetches with local notFound() checks. The profile section page validates supported sections before lookup. Route tests and streaming fixtures cover the updated calls.
SSR response status and diagnostics
apps/web/ssr-degraded.js, apps/web/src/specs/ssr-degraded.spec.ts, apps/web/sentry.server.config.ts, apps/web/src/specs/utils/sentry-config-wiring.spec.ts
Marked lookup-unavailable responses that end with a 5xx status are sent as 503 with Retry-After: 30 and private no-store caching. The handler samples distinct failure details for logs. Server Sentry drops SsrLookupUnavailableError events.
Lookup error boundaries and retry
apps/web/src/app/(dynamicPages)/profile/[username]/error.tsx, apps/web/src/app/(dynamicPages)/community/[community]/error.tsx, apps/web/src/app/waves/[author]/[permlink]/error.tsx, apps/web/src/app/global-error.tsx, apps/web/src/features/shared/lookup-unavailable/*, apps/web/src/features/i18n/locales/en-US.json, apps/web/src/specs/app/lookup-unavailable-error-boundary.spec.tsx
The profile, community, and wave segments use the shared client error component. It reports errors without a digest to Sentry and refreshes the route and resets the boundary on retry. The component renders translated error and retry text.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant ProfilePage
  participant prefetchRequired
  participant QueryClient
  participant ssr-degraded.js
  participant HTTPResponse
  ProfilePage->>prefetchRequired: request required data
  prefetchRequired->>QueryClient: run timed query
  QueryClient-->>prefetchRequired: return query result
  prefetchRequired->>ssr-degraded.js: mark lookup-unavailable with detail
  prefetchRequired-->>ProfilePage: throw SsrLookupUnavailableError
  ssr-degraded.js->>HTTPResponse: send 503 with Retry-After and no-store headers
Loading
Merge Risk: Minimal · previous review 82d7b

No actionable issue remains; the change is ready to merge after normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: failed profile, community, or wave lookups now produce a retryable outcome instead of a 404 response.
Linked Issues check Passed Directly linked issue #2000 is the only active target. Its coding requirements are covered at the reviewed head.

[#2000] Lookup outcome helper: apps/web/src/core/react-query/query-helpers.ts defines …
Out of Scope Changes check Passed Changes that go beyond the page list in #2000 are connected to its objectives:

- Wave page (apps/web/src/app/waves/[author]/[permlink]/page.tsx) and wave error module: these apply the same failed-loo…
Full details: Linked Issues check

Explanation

Directly linked issue #2000 is the only active target. Its coding requirements are covered at the reviewed head.

[#2000] Lookup outcome helper: apps/web/src/core/react-query/query-helpers.ts defines prefetchRequired. Found data is returned. A successful empty result, or a Hive not-found error (isHiveNotFoundError), calls notFound(). An error or timeout calls markSsrDegraded('lookup-unavailable', detail) and throws SsrLookupUnavailableError. It uses the same qc.prefetchQuery call as prefetchQuery, so the layout and page share one fetch.

[#2000] Page switches: the summary lists the profile index, [section] (both branches), followers, following, trail, communities, permissions, settings, referrals, insights, the community index, [tag] (both branches), activities, subscribers and roles as using prefetchRequired. Unknown [section] values are rejected before lookup.

[#2000] Status handling: apps/web/ssr-degraded.js rewrites a lookup-unavailable response with a 5xx status to 503, sets Retry-After: 30 and Cache-Control: private, no-store, and drops conflicting passed headers. 404 and 200 keep their status. The per-minute log line adds up to three sampled error details.

[#2000] Error modules and Sentry: the profile, community and wave segments re-export LookupUnavailableError. The summary states that client errors without a digest are reported through the lazy Sentry facade. The server beforeSend drops SsrLookupUnavailableError.

Acceptance specs named in #2000 exist in the PR. I did not run them. This result relies on the code read at the reviewed head and on the author's reported test runs.

Residual: a late mark on a client-navigation RSC response keeps the route Cache-Control. #2000 does not require a 503 for that case.

Full details: Out of Scope Changes check

Explanation

Changes that go beyond the page list in #2000 are connected to its objectives:

  • Wave page (apps/web/src/app/waves/[author]/[permlink]/page.tsx) and wave error module: these apply the same failed-lookup rule to waves. #2000 does not list waves. The change is not unrelated, because it uses the same prefetchRequired and lookup-unavailable mechanism. The PR states that waves are outside the issue's stated profile and community scope. The author should confirm that this extension is intended.
  • Post pages (/@author/permlink): unchanged. This respects the #2000 exclusion.
  • apps/web/src/utils/hive-not-found-error.ts (NOT_FOUND_ASSERTS): classifies invalid or oversized account-name errors as not-found. prefetchRequired relies on isHiveNotFoundError to choose notFound() for a not-found assert, so this supports the #2000 criterion.
  • ssr-degraded.js log-sample change: requested by #2000 (per-minute log line with a failing-error sample).
  • global-error.tsx comments, en-US.json strings, setup-any-spec.ts SDK mock builders and the render-spec mock updates: supporting documentation, translations, and test scaffolding for the listed changes.
  • Spec files: they test the listed acceptance criteria.

No change was found that is unrelated to #2000 or to its failed-lookup scope.

Full details: Docstring Coverage

Explanation

Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 15 functions across 36 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit checks the lookup trail,
If data hides, it will not fail.
A missing page gets 404,
A shaky call can try once more.
Refresh, reset, and hop away!

Comment @coderabbitai help to get the list of available commands.

- The segment error module catches any error in its routes, so its copy
  names no cause.
- A failed record lookup is counted as lookup-unavailable even when a
  layout's optional prefetch marked the request first.
- Control characters in an upstream error message are removed before the
  sample reaches the log line.
@feruzm
feruzm merged commit c61871c into develop Oct 30, 2026
9 checks passed
@feruzm
feruzm deleted the fix/ssr-lookup-failure-not-404 branch October 30, 2026 20:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Profile and community pages answer 404 when the account or community lookup fails

1 participant