Skip to content

[codex] Avoid blocking route cache misses on cache writes#1548

Open
auggernaut wants to merge 3 commits into
emdash-cms:mainfrom
auggernaut:codex/nonblocking-route-cache-miss
Open

[codex] Avoid blocking route cache misses on cache writes#1548
auggernaut wants to merge 3 commits into
emdash-cms:mainfrom
auggernaut:codex/nonblocking-route-cache-miss

Conversation

@auggernaut

@auggernaut auggernaut commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Schedules Cloudflare route-cache MISS writes with waitUntil() instead of awaiting cache.put() before returning the rendered response.

The Cloudflare cache runtime was rendering fresh responses on route-cache misses, then waiting for Cache API storage before sending the page. If Cloudflare cache storage was slow or stalled in a colo, an otherwise valid page response could hang. Cache writes are best-effort optimization work, so they should not block visitor responses.

Closes #

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/...

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: GPT-5 Codex

Screenshots / test output

  • pnpm --filter emdash build
  • pnpm --filter @emdash-cms/cloudflare test
  • pnpm --filter @emdash-cms/cloudflare typecheck
  • pnpm --filter @emdash-cms/cloudflare build

@changeset-bot

changeset-bot Bot commented Jun 19, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1bc2791

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 16 packages
Name Type
@emdash-cms/cloudflare Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
emdash Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jun 19, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1548

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1548

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@1548

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1548

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1548

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@1548

emdash

npm i https://pkg.pr.new/emdash@1548

create-emdash

npm i https://pkg.pr.new/create-emdash@1548

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1548

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@1548

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@1548

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@1548

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@1548

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@1548

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1548

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1548

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1548

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1548

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1548

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1548

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@1548

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1548

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1548

commit: 1bc2791

@auggernaut auggernaut changed the title Avoid blocking route cache misses on cache writes [codex] Avoid blocking route cache misses on cache writes Jun 19, 2026
@auggernaut auggernaut marked this pull request as ready for review June 19, 2026 14:19
@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label Jun 19, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approach

The change is sound and fits EmDash's conventions. Cache writes on a route-cache MISS are exactly the kind of maintenance/best-effort work that AGENTS.md says shouldn't block TTFB ("Defer bookkeeping … Maintenance writes don't need to block TTFB"), and moving cache.put() behind waitUntil() is the idiomatic Cloudflare pattern for that. It's also consistent in intent with the existing SWR revalidation path in the same file, which already defers its background cache.put() via waitUntil(). So this is the right change solving the right problem.

What I checked

  • runtime.ts MISS path (line 278): waitUntil(cache.put(cacheKey, toStore)) correctly does not block the returned miss response. Body/stream semantics are safe: toStore is built from response.clone() while miss reads response.body, so the background write and the client response consume independent tee'd streams — no double-read.
  • Sibling SWR path (lines 243–257): traced for consistency. It defers cache.put() the same way but wraps it in a try/catch ("Non-fatal — next request will retry").
  • invalidate(): still awaits cache.delete() — correct, since explicit invalidation callers expect completion; not a candidate for deferral.
  • New test (cache-runtime.test.ts): genuinely verifies the behavior, not false confidence. cache.put is mocked to never resolve, and the test asserts the response still returns with the MISS header and body while waitUntil is called once with a promise. Under the old await cache.put(...) this test would hang, so it would catch a regression. The cloudflare:workers/caches mocks line up with what the runtime imports.
  • Changeset: present, targets @emdash-cms/cloudflare (patch), leads with a present-tense verb and states the observable effect — consistent with existing changesets in the repo.

Headline

Implementation is correct and the test is solid. One issue: the MISS path hands cache.put() to waitUntil() without a try/catch, unlike the SWR path directly above it and unlike AGENTS.md's rule that deferred work be wrapped in try/catch. A rejecting cache.put becomes an unhandled rejection in the waitUntil promise (noisy error reports in production, even though the visitor's response is already sent). The fix is trivial and mirrors the SWR path.

Comment on lines 277 to 279
if (toStore) {
await cache.put(cacheKey, toStore);
waitUntil(cache.put(cacheKey, toStore));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[needs fixing] The MISS path schedules the write with waitUntil(cache.put(cacheKey, toStore)) but — unlike the SWR revalidation path directly above (lines 243–257, which wraps cache.put in a try { … } catch { // Non-fatal — next request will retry }) — it has no error handling. AGENTS.md requires deferred work to be wrapped in try/catch ("Wrap your function body in try/catch"). If cache.put rejects (storage error, aborted write), the promise handed to waitUntil rejects unhandled, surfacing as an unhandled-rejection error report in production even though the visitor's response has already been returned. The same "non-fatal, next request retries" rationale the SWR path documents applies identically here. Mirror the SWR path:

Suggested change
if (toStore) {
await cache.put(cacheKey, toStore);
waitUntil(cache.put(cacheKey, toStore));
}
if (toStore) {
waitUntil(
(async () => {
try {
await cache.put(cacheKey, toStore);
} catch {
// Non-fatal — next request will retry
}
})(),
);
}

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant