Skip to content

fix(content-serve): degrade missing-BLOB race to styled 404, not raw 500 (#3103) - #3105

Merged
jung-thomas merged 2 commits into
DEVfrom
worktree-fix-3103-missing-blob-500
Nov 11, 2026
Merged

jung-thomas merged 2 commits into
DEVfrom
worktree-fix-3103-missing-blob-500

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

Summary

Fixes #3103. Two compounding defects on /tutorials/:slug and concept/page serve paths:

Defect 1 — serveStoredSlug throws on missing BLOB

The serve path reads BLOB metadata and the BLOB itself in two separate queries (correct — avoids HANA LOB-locator-expiry gotcha). During a concurrent delta-write to the mutable ContentCurrent (overwrite-in-place, no version pin) or a transient HANA locator blip, the metadata SELECT can hit while the separate BLOB SELECT returns nothing. This caused toBuffer(null) / blobRow.CONTENT dereference to throw.

Defect 2 — serveHandler catch surfaces raw JSON 500

serveHandler's catch was res.status(500).json({ error: 'Content retrieval failed' }) — bare JSON, not the styled error page. All sibling branches (pageServeHandler, authorServeHandler, advocateServeHandler) already call serveNotFound; this path was the outlier.

Fix

  • packages/content/content-store.js — serveStoredSlug: guard the BLOB read — null/undefined/non-Buffer/zero-length contentBuf → return 'not-found' (styled 404) instead of throwing. Emits content.blob.missing metric for observability.
  • serveHandler catch: replace raw JSON 500 with serveNotFound(res, slug, 500) — styled page, status 500 preserved for monitoring.

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

Test

test/lib/content-store.test.js — publishes a slug, nulls its content column (metadata intact), asserts HTTP 404 not 500. Red before, green after. Full 56-test suite passes.

…500 (#3103)

Two compounding defects on /tutorials/:slug and /concepts/:slug serve paths:

1. serveStoredSlug read the BLOB metadata and the BLOB itself in separate
   queries (correct — avoids HANA LOB-locator-expiry). During a concurrent
   delta-write to mutable ContentCurrent (overwrite-in-place, no version pin)
   or a transient HANA locator blip the metadata row could survive while the
   BLOB SELECT returned null/undefined, causing toBuffer(null) or
   blobRow.CONTENT dereference to throw.

2. serveHandler's catch returned res.status(500).json({error:'Content retrieval
   failed'}) — raw JSON. All sibling branches (pageServeHandler,
   authorServeHandler, advocateServeHandler) already call serveNotFound; this
   path was the outlier.

Fix:
- Guard the BLOB read: null/undefined/non-Buffer/zero-length contentBuf →
  return 'not-found' (styled 404). Emits content.blob.missing metric for
  observability of the race condition.
- Align serveHandler catch with siblings: serveNotFound(res, slug, 500)
  (styled page, status 500 preserved for monitoring).

Test: publish a slug, null its content column (metadata intact), assert
HTTP 404 not 500. Red before, green after.
…ing-blob-500

# Conflicts:
#	packages/content/content-store.js
@jung-thomas
jung-thomas merged commit 4050ce3 into DEV Nov 11, 2026
4 checks passed
@jung-thomas
jung-thomas deleted the worktree-fix-3103-missing-blob-500 branch November 11, 2026 19:11
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