Skip to content

Publish conflicts and recovery

If Publish fails, keep the editor open and preserve your work before reloading or discarding. A content conflict and a failed deployment need different fixes.

Problem Next step
Someone changed the entry or Markdown source Copy your draft, load current content, and reapply the intended changes
Publication stopped after a storage error Restore storage access and retry Publish; do not remove its recovery data
recovery_conflict Ask the site operator to reconcile the saved recovery plan with current content
Content published but rebuilding failed Use Retry deploy, or retry through your deployment service

Discard removes saved work. The toolbar discards all drafts owned by the current editor. Do not use it as a generic fix for a publish error.

The rest of this page explains these cases for developers and operators. For normal editing, see Saving and publishing.

A structured draft remembers the published entry as it was when you first edited it, including its revision number. Publish checks that the entry has not changed. If it has, the whole draft is rejected with stale_entry, even if the other editor changed a different field. CaretCMS does not merge those changes for you. Markdown prose uses checks against its original source text and positions.

Structured drafts created before baseline tracking are retained but rejected with legacy_draft. Body-only legacy drafts can still publish if their source hashes match. Private baseline and recovery keys never become public entry data and cannot be supplied through mutation commands.

On a conflict, copy the draft edits somewhere safe before discarding them, reload the latest content, and reapply the intended changes. The toolbar’s Discard button removes all of the editor’s drafts. The authenticated DELETE /api/cms/draft?collection=…&id=… endpoint can discard a single entry. A conflicted draft is not automatically discarded or overwritten.

Publication persists a plan in the editor’s overlay before writing base content. The plan contains the before/after data and, for Markdown, the original and prepared source, plus a stable history operation ID. Under the entry lock it:

  1. Writes the prepared content.
  2. Advances the base revision once.
  3. Appends the original state to history once.
  4. Clears the draft and recovery plan.

A failed step retains the plan. Restore storage access and click Publish again. A retry checks stored content, source, revision, and history to finish the remaining steps without reapplying a Markdown splice or incrementing twice. Edits and discards of that draft are blocked until recovery finishes. Filesystem overlays persist this state across adapter/server restarts; in-memory overlays last only as long as the process.

If another writer or source edit intervened during recovery, the response reports recovery_conflict and leaves the plan untouched. An operator must compare the stored before/after plan with current content and restore a consistent state before retrying. Preserve a backup of both before making that repair; there is no automatic merge or force-publish command.

POST /api/cms/publish returns separate published, conflicts, and failed arrays. HTTP 200 / ok: true means the request was processed, not that every entry succeeded. A failed entry may already have changed base content. The browser reports that distinction and keeps recovery available. Bulk publishing continues to other entries and reports completed entries even if another fails. Git journaling and rebuild hooks receive only the entries whose publication finished.

Recovery finishes an interrupted publication rather than rolling it back. Publishing several entries is not one all-or-nothing operation. The storage must preserve successful writes, return consistent reads, and coordinate its writers. The built-in filesystem implementation uses atomic file replacement. Cloudflare KV retains its documented single-writer and eventual-consistency limitations; this change adds no distributed lock.

delivery.publish.timeoutMs defaults to 5000 ms, clamped to 1–30000 ms. Timeouts, network failures, and non-success HTTP responses do not roll back published content. Browser errors never include configured authorization headers or the hook URL.

After publication, adapters with getRebuildReceipt() / setRebuildReceipt() retain the hook payload in editor-specific metadata before calling the hook. The built-in filesystem, in-memory, and Cloudflare KV/Durable Object editor overlays implement these optional methods. Existing third-party adapters remain compatible; without these methods, retry the deployment through your deployment service.

Retry deploy sends authenticated, CSRF-protected POST /api/cms/publish with { "retryRebuild": true }. It reuses the pending payload without republishing content, changing revisions, or adding history. It works after drafts are cleared and, with persistent storage, after reload. A later successful publish also includes pending entries in its hook payload.

Delivery is not exactly once. A timeout may occur after the receiver accepted the request; a receipt-clear failure may also leave an already accepted request available to retry. Receivers should deduplicate using collection/ID/revision or commit. Receipts are stored after content publication, so a crash before receipt persistence, or a receipt write failure (receiptError: true), can require a manual deployment outside Caret. There is no automatic background retry. Hook success confirms HTTP acceptance only, not a completed/live deployment.