Repository navigation
fix(core): unschedule a scheduled publish that publishing refuses - #4782
eisenbruch wants to merge 3 commits into
Conversation
When the scheduler's publish fails with VALIDATION_ERROR or SLUG_CONFLICT, the entry is unscheduled and the error message is recorded, as a plugin's content:beforePublish rejection already is. Before, the error went only to the log, every tick retried it, and the calendar said scheduled publishing might not be running. The stored rejection's pluginId becomes optional and gains a code. The dashboard notice no longer says the cause was a publication policy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RktNvhjty22ohh1GSHXGMq
🦋 Changeset detectedLatest commit: fdf892b 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.
Both reviewers agree that routing VALIDATION_ERROR and SLUG_CONFLICT publish refusals through the existing scheduled-rejection flow is the right approach. It unschedules the entry, records the reason, and shows the dashboard notice, with no second mechanism. Neither found a confirmed runtime regression, and both reviewed statically. Three items need fixing: the changeset omits a public type change (ScheduledPolicyRejection.pluginId is now optional), the VALIDATION_ERROR case has no regression test, and a new helper has a narrative docblock that breaks the comment convention. One dashboard string still says "blocked" and is a minor suggestion.
Reviewed by anthropic/claude-opus-5-5 and openai/gpt-6.1-sol.
Findings
-
[needs fixing]
.changeset/scheduled-publish-refusal.md:6ScheduledPolicyRejectionandisScheduledPolicyRejectionare part of the publicemdashAPI (packages/core/src/index.tslines 290 and 403). This PR makesScheduledPolicyRejection.pluginIdoptional and adds an optionalcodefield.Any integration that reads the
emdash:scheduled-policy-rejection:*options will be affected:- After narrowing with
isScheduledPolicyRejection,rejection.pluginIdis now typedstring | undefined, so the TypeScript build fails. - At runtime, the new records have no
pluginIdat all.
The changeset only describes the dashboard behavior. Per
.changeset/README.md("Name the user-facing API…") and AGENTS.md's backwards-compatibility rule, it should name this API change and tell consumers what to do. For example, add:ScheduledPolicyRejection.pluginIdis now optional: records written for a refused publish have nopluginIdand carry the publish error'scodeinstead. Code that reads these records should handle a missingpluginId. - After narrowing with
-
[suggestion]
packages/admin/src/components/Dashboard.tsx:220The PR renames the banner because "Publication policy blocked" is wrong for a slug conflict. The overflow line still says "One more blocked entry is not shown." / "# more blocked entries are not shown.", which uses the old policy wording for entries that may have been refused for validation or slug reasons. Consider matching the new title, for example "One more entry is not shown." / "# more entries are not shown."
-
[needs fixing]
packages/core/tests/unit/scheduled-publish.test.ts:821The new runtime tests exercise
SLUG_CONFLICT, but none produces theVALIDATION_ERRORthat caused the reported production failure. The dashboard test only supplies a prebuilt rejection record, so removingVALIDATION_ERRORfrom the refusal set would leave all these tests passing while restoring that failure. Please add a real-database regression throughrunScheduledTasks()for a validation refusal—for example, a due routable entry with a blank staged slug—and assert that its schedule is cleared and its validation message is persisted in a dashboard-readable rejection. This covers the reported behavior as required by AGENTS.md's regression-evidence rule. -
[needs fixing]
packages/core/src/emdash-runtime.ts:4982-4988This docblock narrates the bug and summarizes the helper's implementation ("the calendar would … guess that the scheduler is down", "the entry is unscheduled and the error message is recorded"). AGENTS.md's Comments convention explicitly excludes PR-style narrative and restatements of what the code does. Please remove it; the helper name, refusal-code set, and calls already express this behavior. If a comment is needed, keep only a non-obvious invariant that a future maintainer could otherwise miss.
Add a VALIDATION_ERROR regression through runScheduledTasks(), name the ScheduledPolicyRejection type change in the changeset, drop the helper's narrative docblock, and stop calling the overflow entries "blocked". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RktNvhjty22ohh1GSHXGMq
|
Thanks, all four are in
Core unit tests: 512 files, 6,764 passed. Admin 🤖 Generated with Claude Code |
There was a problem hiding this comment.
Routing permanent scheduled-publish refusals through the existing rejection flow remains the right approach and fits EmDash’s architecture.
All previous findings are addressed: the changeset explains the public record/type change and how consumers should adapt; the new real-database regression test exercises VALIDATION_ERROR through runScheduledTasks() and checks unscheduling, draft preservation, and the persisted reason; the narrative helper comment is removed; and the overflow wording matches the broader notice.
I reviewed the follow-up diff and traced the new test against the publication validation path. No new issues found. Review was static; checks were not run.
Follow-up review by openai/gpt-6.1-sol, scoped to the changes since the last review.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RktNvhjty22ohh1GSHXGMq
What does this PR do?
Closes #4781.
When the scheduler's publish is refused with
VALIDATION_ERRORorSLUG_CONFLICT,EmDashRuntime.handleContentPublishnow treats it like acontent:beforePublishcancellation: it unschedules the entry throughhandleScheduledPolicyRejection, records the error message, refreshes media usage and runscontent:afterUnschedule. The dashboard's existing notice lists the entry with the message, and a later successful schedule or publish clears it, as for plugin rejections.CONFLICT,NOT_DUE,CONTENT_PUBLISH_ERRORand anything else leave the schedule for the next pass, as today.ScheduledPolicyRejection.pluginIdbecomes optional and the record gains an optionalcode. A plugin rejection is written exactly as before.isScheduledPolicyRejectionaccepts a record withoutpluginId.toScheduledRejectionReasonturns control characters into spaces and cuts the message to the 500-code-point limit. OtherwiseisScheduledPolicyRejectionwould hide the record from the list while the count still included it.handleScheduledPolicyRejection), the failure is logged, and the next pass tries again.reference/content-lifecycle.mdxdescribes the new case next to the plugin one.The calendar's "Scheduled publishing may not be running" banner is unchanged. A refused entry is no longer scheduled, so it no longer reaches that banner.
Type of change
Your connection to this change
Checklist
pnpm typecheckpasses (tsgo --noEmitin core and admin)pnpm lintpasses (oxlint --type-aware --deny-warningson the changed files)pnpm testpasses (or targeted tests for my change): core unit tests, 512 files, 6,764 passed; adminDashboard.test.tsx28 passedpnpm formathas been run (oxfmt --ignore-path .gitignore --check .clean)messages.pochanges except in translation PRs — a workflow extracts catalogs on merge tomain.AI assistance disclosure
Screenshots / test output
Taken on
pnpm ux:site:start(editorial-small). That site does not run the scheduler, so both states were set up in its database: a scheduled post, due two hours earlier, whose staged slug another post holds.Before (
main): the post stays scheduled, and the calendar panel says the scheduler may not be running.After (this branch): the record the scheduler now writes for that post, as the dashboard lists it.
Tests (
tests/unit/scheduled-publish.test.ts), throughrunScheduledTasks()on a post whose staged slug another post holds:code: "SLUG_CONFLICT", the message, and nopluginId;VALIDATION_ERROR("Cannot publish routable content without a slug"): its schedule is cleared, its draft kept, and the message recorded;CONTENT_PUBLISH_ERROR) leaves the post scheduled with no record;toScheduledRejectionReasonhandles control characters, the length limit and empty input.Without the runtime change, the first three fail; with
VALIDATION_ERRORtaken out of the refusal codes, the blank-slug test fails. Admin: a record with acodeand nopluginIdis listed with its message.🤖 Generated with Claude Code
https://claude.ai/code/session_01RktNvhjty22ohh1GSHXGMq