Skip to content

fix(core): defer afterDelete and afterUnpublish hooks so Workers doesn't cancel them#1588

Draft
marcusbellamyshaw-cell wants to merge 1 commit into
emdash-cms:mainfrom
Emdash-Bug-Testing:fix/afterdelete-hooks-canceled-on-workers
Draft

fix(core): defer afterDelete and afterUnpublish hooks so Workers doesn't cancel them#1588
marcusbellamyshaw-cell wants to merge 1 commit into
emdash-cms:mainfrom
Emdash-Bug-Testing:fix/afterdelete-hooks-canceled-on-workers

Conversation

@marcusbellamyshaw-cell

Copy link
Copy Markdown
Contributor

What does this PR do?

runAfterDeleteHooks and runAfterUnpublishHooks dispatch plugin hooks fire-and-forget (a bare promise with .catch()), unlike runAfterSaveHooks and runAfterPublishHooks, which already wrap their dispatch in after().

On Cloudflare Workers a promise that isn't handed to the host's lifetime extender (waitUntil, which after() wraps) is canceled the moment the HTTP response is returned. So any real work a plugin does in content:afterDelete / content:afterUnpublish — deleting uploaded files from R2, removing search-index entries, plugin-storage writes — gets killed mid-flight. In practice a half-completed plugin-storage write during afterDelete could wedge the storage backend for the isolate and hang every subsequent authenticated admin request (observed as the admin "Move to trash" action freezing the content list).

The fix wraps both dispatchers in after(), identical to the existing afterSave / afterPublish handling — trusted hook in a try/catch, sandboxed plugins collected into tasks and awaited via Promise.allSettled.

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 (0 diagnostics)
  • pnpm test passes (full core suite: 4071 passed; the only failures are pre-existing Windows-env issues — file-URL/tar/file-SQLite — that fail identically on a clean main)
  • 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 (n/a — no UI strings)
  • I have added a changeset
  • New features link to an approved Discussion (n/a — bug fix)

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Claude Opus 4.8 (Claude Code)

Screenshots / test output

New test tests/integration/runtime/after-hooks-deferred.test.ts drives a real soft-delete and unpublish through EmDashRuntime and asserts the hook work is scheduled via after() rather than run inline-and-abandoned. It fails on stock main (the handler runs inline) and passes with this change.

 Test Files  1 passed (1)
      Tests  2 passed (2)

…n't cancel them

runAfterDeleteHooks and runAfterUnpublishHooks dispatched plugin hooks
fire-and-forget. On Cloudflare Workers a promise not handed to waitUntil is
canceled when the response returns, so plugin cleanup work (R2 deletes,
search-index removal) is killed mid-flight -- and a half-completed
plugin-storage write can wedge the isolate and hang later admin requests.

Wrap both dispatchers in after(), exactly as runAfterSaveHooks and
runAfterPublishHooks already do.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jun 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7259317

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

This PR includes changesets to release 16 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd 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-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/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 23, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

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

@emdash-cms/auth

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

@emdash-cms/auth-atproto

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

@emdash-cms/blocks

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

@emdash-cms/cloudflare

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

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

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

emdash

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

create-emdash

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

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

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

@emdash-cms/plugin-cli

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

@emdash-cms/plugin-types

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

@emdash-cms/registry-client

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

@emdash-cms/registry-lexicons

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

@emdash-cms/sandbox-workerd

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

@emdash-cms/x402

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

@emdash-cms/plugin-ai-moderation

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

@emdash-cms/plugin-atproto

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

@emdash-cms/plugin-audit-log

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

@emdash-cms/plugin-color

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

@emdash-cms/plugin-embeds

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

@emdash-cms/plugin-field-kit

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

@emdash-cms/plugin-forms

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

@emdash-cms/plugin-webhook-notifier

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

commit: 7259317

@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Jun 24, 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.

This is the right fix for the right problem. afterDelete and afterUnpublish hook dispatch was the only remaining lifecycle cleanup path that dispatched on a bare promise, so on Cloudflare Workers it could be canceled as soon as the HTTP response went out. Wrapping both in after() matches the existing afterSave / afterPublish pattern exactly, which is the documented way to defer bookkeeping past the response and hand the lifetime to waitUntil.

I read the full diff, the after() implementation, the existing runAfterSaveHooks / runAfterPublishHooks implementations, and the new test. The code changes mirror the established pattern closely: trusted hook awaited with try/catch, sandboxed plugins collected into tasks and awaited via Promise.allSettled, and the entry-point comments updated to reflect deferred execution. The test directly verifies the scheduling contract by mocking after() and asserting the hook handler is not run inline but is flushed from the deferred queue, and it fails on the pre-fix behavior.

No blocking issues, no AGENTS.md convention violations, no SQL/Tailwind/Lingui concerns, and the changeset is present and correctly names the emdash package. LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core bot:review Trigger an emdashbot code review on this PR cla: signed size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants