Repository navigation
Conversation
🦋 Changeset detectedLatest commit: 911ce98 The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 packages
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 |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-test
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/registry-loader
@emdash-cms/registry-moderation
@emdash-cms/registry-verification
upgrade-emdash
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-ai-search
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
There was a problem hiding this comment.
Batching plugin content-action cache tags per cron tick is the right approach for #4408. Tags are collected in an AsyncLocalStorage scope, deduplicated, and purged once in finally. Late writes fall through to an immediate purge. This fits the shared-runtime architecture better than mutating the global invalidator. Both reviewers found the same regression outside the cron batch. The PR removes the line that stored options.invalidateContentCache as the runtime's default invalidator, and the new scope covers only runPluginCronTick. Plugin content actions triggered by scheduled publishing's content:afterPublish hooks, which run afterward in runScheduledMaintenanceTick, therefore get no invalidator and never purge on Cloudflare. A regression test for that path is needed. One reviewer also noted a non-blocking trade-off. Purges are deferred to the end of the batch, so if the Worker hits its CPU or wall-clock limit mid-tick, plugin writes that already committed get no purge. Flushing after each task would bound that exposure. Both reviewers reviewed statically.
Reviewed by anthropic/claude-opus-5-5 and openai/gpt-6.1-sol.
| if (this.cronExecutor) { | ||
| try { | ||
| processed = await this.cronExecutor.tick(); | ||
| processed = await this.runPluginCronTick(options.invalidateContentCache); |
There was a problem hiding this comment.
[needs fixing] This PR removes if (options.invalidateContentCache) this.pluginContentCacheInvalidator = options.invalidateContentCache;. No production code calls setPluginContentCacheInvalidator, so that line was the only thing that set the fallback used by runPluginContentAction on Cloudflare.
The new ALS scope only covers runPluginCronTick. Scheduled publishing runs afterwards, in runScheduledMaintenanceTick → publishScheduledWithFence → handleContentPublish. That publish fires content:afterPublish hooks through runDeferredContentHook.
- Trusted plugin hooks: content actions in these hooks (
update,addEntryTerms,publish) get no explicit invalidator, no invocation invalidator, and no ALS store. - Sandboxed plugin hooks:
invokeHookcallsbeginwithout an invalidator, so they end up in the same state.
Before this PR, those writes fell back to invalidateContentTags from createScheduledHandler. Now the invalidator is undefined, so they never purge the cache. Plugin edits made in response to scheduled publishes stay stale at the edge. onPublished cannot cover this: it receives only entries promoted by the scheduled sweep, not the hook's additional writes.
One fix is to keep the invocation's immediate (non-collecting) invalidator available during maintenance and its deferred hooks, overriding it with the collector only for the plugin cron tick. For example, run runScheduledMaintenanceTick inside an immediate scheduledContentCacheInvalidator.run(options.invalidateContentCache, …), or pass the invalidator through explicitly. That keeps the PR's goal of not mutating the shared default from a scheduled invocation.
Please add a regression test: a scheduled publication's content:afterPublish hook performs a content action on another, unscheduled entry (for example in another collection) during a scheduled-publish sweep. Verify that entry's tags reach invalidateContentCache.
f30f338 to
911ce98
Compare
There was a problem hiding this comment.
The follow-up takes the right approach: maintenance receives an invocation-scoped immediate invalidator, while plugin cron actions retain their deduplicated batch.
The earlier finding is addressed. I traced scheduled publishing through deferred hooks and invocation invalidator capture, and reviewed the added trusted and sandboxed regression tests, including callbacks arriving outside the scheduler’s async context. The tests cover hook writes after maintenance returns without relying on onPublished to invalidate those additional writes.
No further issues found in the follow-up. Reviewed statically; I did not run tests or verify live Cloudflare behavior.
Follow-up review by openai/gpt-6.1-sol, scoped to the changes since the last review.
What does this PR do?
Batches plugin content-action cache invalidation within each
runScheduledTasks()cron batch. Tags are deduplicated and flushed once infinally, including after a task or executor bookkeeping failure. This reduces the purge calls generated by plugin import jobs.The invalidator is scoped to the scheduled invocation. Concurrent HTTP actions remain immediate, sandbox callbacks retain the batch across RPC, and late hook writes invalidate immediately after collection closes. Scheduled publishing keeps its existing per-collection
onPublishedcallback.Closes #4408
Type of change
Your connection to this change
I want to help resolve the problem another EmDash user reported in #4408. This contribution reproduces the repeated purge calls with real plugin cron tasks and SQLite, adds a fix, and verifies the behavior with regression tests. I am contributing as a community member; I am not claiming personal production impact, an integration-maintainer role, or prior Maintainer approval.
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (or targeted tests for my change)pnpm formathas been runmessages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain.No admin strings or UI changes; translation and screenshots are not applicable. This fixes reproduced repeated purge calls without adding a public API; no feature plan is linked.
AI assistance disclosure
Screenshots / test output
Screenshots: Not applicable.
Before:
scheduled-cache-invalidation.test.tsran real plugin cron tasks against SQLite. Two publications made two invalidator calls; the regression failed withNumber of calls: 2where one deduplicated call was expected.After: 60 targeted tests pass across scheduler, cron correctness, route invalidation, and sandbox action settlement. Coverage includes task failures, executor bookkeeping failures, empty and successive ticks, provider errors, concurrent HTTP writes, late hook writes, sandbox callbacks arriving outside the scheduler's async context, and trusted/sandboxed scheduled-publication hooks that write after maintenance returns.
Validation:
pnpm formatpnpm buildfrom repository rootpnpm lint:quickpnpm --silent lint:json— zero diagnostics--workspace-concurrency=1pnpm --filter emdash exec vitest run tests/integration/runtime/scheduled-cache-invalidation.test.ts tests/integration/runtime/plugin-action-settlement.test.ts tests/integration/astro/plugin-publication-cache-invalidate.test.ts tests/unit/scheduled-publish.test.ts tests/integration/runtime/plugin-cron-route.test.ts tests/integration/plugins/cron-correctness.test.tsLocal tests verify content commits and purge callback counts. Live Cloudflare rate-limit errors and the reporter's CRM feed were not exercised. Automated review found that scheduled-publication after-hooks had lost their invalidator. Maintenance now retains an immediate invalidator in its own async scope; two added regressions fail with zero purges on the previous implementation and pass with the fix. Hosted provider behavior remains unverified.
Batching trade-off: a Worker killed by a hard CPU or wall-clock limit before the final flush can leave already committed cron writes unpurged. Ordinary task/executor errors still execute the final flush; scheduled publishing keeps incremental per-collection invalidation.