Skip to content

fix(content): degrade missing-BLOB race to styled page, not raw 500 (#3103) - #3104

Merged
jung-thomas merged 1 commit into
DEVfrom
worktree-fix-content-serve-500-resilience
Nov 11, 2026
Merged

jung-thomas merged 1 commit into
DEVfrom
worktree-fix-content-serve-500-resilience

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

Closes #3103.

Problem

Intermittent raw HTTP 500 "Internal Server Error" on content page loads (e.g. /concepts/ui5-supportrc-file/, /tutorials/:slug). Found while debugging live PROD 500s on 2026-11-11.

Two compounding defects:

  1. serveStoredSlug reads BLOB metadata and the BLOB itself in two separate queries (to avoid the HANA LOB-locator-expiry gotcha). When metadata hits but the separate BLOB read returns nothing — the inconsistent-read window during a concurrent publish/delta-write to the mutable ContentCurrent (overwrite-in-place, no version pin), or a transient HANA locator blip — blobRow.CONTENT / toBuffer(null) threw.
  2. serveHandler's catch surfaced raw JSON {"error":"Content retrieval failed"} instead of the styled page. The sibling pageServeHandler/authorServeHandler/advocateServeHandler all fail open — the tutorial/concept path was the outlier. No global Express error middleware backstops it.

Net: a transient, self-resolving infra condition showed users a bare "Internal Server Error."

Fix

  • serveStoredSlug — guard null/missing/non-Buffer/zero-length BLOB → return 'not-found' (styled 404); emit content.blob.missing metric. Both HANA and SQLite branches now coerce via toBuffer and share the Buffer.isBuffer guard.
  • serveHandler catch — serveNotFound(res, slug, 500) (styled page, 500 kept for monitoring) instead of raw JSON. Mirrors the catalog branch.
  • serveNotFound — Cache-Control: no-store on 5xx so a transient blip served as a styled 500 is never edge-cached for 60s (404s stay cacheable).
  • resolveContentBlob — same toBuffer(null) guard in both branches (the error-page/nav resolver had the identical latent bug).

Covers srv and srv-qa (both shim over @tutorials/content).

Test

test/lib/content-store.test.js: publish a slug, null its content column (metadata intact), assert styled 404 not 500. Red before, green after. Full content-store suite 56/56; render-cache + catalog-slugs suites green (62/62 total).

Not in scope

The admin/API JSON endpoints (/content/hashes, /content/nav, publish, rollback, markdown) keep raw-JSON 500s — those are machine/admin consumers, not browser page loads. Related error-log noise (cds-caching unique-constraint) filed separately as #3102.

🤖 Generated with Claude Code

…3103)

Content page loads (/concepts/:slug, /tutorials/:slug) intermittently
returned a raw 500 "Internal Server Error". Root cause: serveStoredSlug
reads BLOB metadata and the BLOB in two separate queries (to avoid the
HANA LOB-locator-expiry gotcha). When metadata hits but the separate
BLOB read returns nothing — the inconsistent-read window during a
concurrent publish/delta-write to the mutable ContentCurrent, or a
transient HANA locator blip — blobRow.CONTENT / toBuffer(null) threw,
and serveHandler's catch surfaced raw JSON instead of the styled page.

- serveStoredSlug: guard null/missing/non-Buffer/zero-length BLOB →
  return 'not-found' (styled 404); emit content.blob.missing metric.
  Both HANA and SQLite branches now coerce via toBuffer + share the guard.
- serveHandler catch: serveNotFound(res, slug, 500) (styled page, 500
  kept for monitoring) instead of raw JSON — mirrors the catalog branch
  and the fail-open pages path.
- serveNotFound: no-store on 5xx so a transient blip served as a styled
  500 is never edge-cached for 60s (404s stay cacheable).
- resolveContentBlob: same toBuffer(null) guard in both branches, so the
  error-page/nav resolver can't throw on the identical race.

Covers srv and srv-qa (both shim over @tutorials/content).
Test: publish a slug, null its content column (metadata intact), assert
styled 404 not 500. Red before, green after.
@jung-thomas
jung-thomas merged commit cce0102 into DEV Nov 11, 2026
4 checks passed
@jung-thomas
jung-thomas deleted the worktree-fix-content-serve-500-resilience branch November 11, 2026 18:44
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.

1 participant