← All articles

Debugging & Postmortems / 2026.08.06 / 6 min read

I Added a Publish Gate That Won't Fire on Its Own

'Save to draft' and 'broadcast for real' must be two different authorizations; and after the API returns success, read the real status back from the platform once more.

I Added a Publish Gate That Won't Fire on Its Own — Kenton Blog

After a request times out, is it a success or a failure?

The hardest case in a publisher often has no red error. The request went out, but the connection dropped before the full result came back.

The platform may have created the draft, or nothing may have happened. The only thing locally certain is that I didn't get a complete response. If the script, out of ordinary network-request habit, auto-retries here, the account very likely gains a second draft; swap in a broadcast and the cost is much higher.

So in this situation my publisher doesn't show failed — it stops at unknown:

unknown isn't the same as failed, and it absolutely isn't a reason to submit again.

Every safety design after this starts from that uncertainty.

First, split the four actions apart

Content automation often overloads the word "submit," but these four actions have completely different risk:

  1. Generate the local body: only changes a local file.
  2. Upload assets: sends images or a cover to the platform.
  3. Save a platform draft: editable content appears in the target account.
  4. Publish or broadcast for real: content becomes publicly visible, possibly impossible to retract cleanly.

Buttons, commands and logs should all use accurate action names. A button called "submit" that might broadcast behind the scenes leaves the operator unable to make an informed choice.

My default capability stops at saving a draft. Publishing is a separate entrance that needs new authorization.

The gate isn't one confirm box — it's four layers of evidence

Identity gate: confirm the target account

The system can't just show an internal appid or a config filename. Before running, let a person see a recognizable account name and environment — for example "test account" versus "production account."

When the target account can't be confirmed, the flow fails closed; it doesn't pick the first default account.

Content gate: freeze the current version

What gets authorized isn't a title, but a set of fixed content:

  • The title.
  • The summary.
  • The body version or hash.
  • The cover file.
  • The target account.
  • The action type.

If the body or cover changes after authorization, the old authorization expires automatically. Otherwise a person confirms version A and the system may publish version B.

Action gate: separate draft and broadcast permissions

The credential and entrance for saving a draft shouldn't naturally gain broadcast permission. Even if the platform technically uses a similar API, treat them as two permission levels in the business sense.

"Allowed a broadcast last time" can't become a long-term default. Every broadcast binds to the current content and the current account.

Evidence gate: read back from the platform after submitting

A local file only proves the content is ready.

An API response only proves the platform returned some result.

A platform read-back proves a corresponding draft or public article exists in the target account.

The evidence gate requires saving the platform ID, title, creation time, status and the necessary content digest. Only when these match the local version does the status get upgraded.

The state machine I use

Status names must show how much the system knows:

prepared
authorized_for_draft
draft_submitted
draft_confirmed
authorized_for_publish
publish_submitted
published_confirmed
failed
unknown

Statuses can't jump arbitrarily. For example, prepared can't go straight to published_confirmed; and draft_submitted can't skip the read-back just because something locally said "success."

failed and unknown are handled differently:

  • failed: there's clear error evidence. After fixing, still confirm the previous attempt wasn't received by the platform before retrying.
  • unknown: evidence is insufficient. Reconcile first; don't auto-retry.

This distinction adds a few lines of code, but it avoids the hardest-to-recover duplicate action.

How I handle one timeout

Say the save-draft request times out after 30 seconds.

Step 1: freeze this run

Don't change the body, don't regenerate the cover, don't switch accounts. Any change makes reconciliation harder.

Step 2: save a local receipt

The receipt records at least:

  • The run ID.
  • The content version.
  • The target account.
  • The action type.
  • The request start time.
  • Any platform ID already obtained.
  • The current status, unknown.
  • An error digest with sensitive fields hidden.

Step 3: query the platform read-only

Prefer the draft-read API; without one, open the current account's backend and reconcile by time, title and assets.

Querying by title alone isn't enough, because multiple versions may share a title. The platform ID and creation time are more reliable.

Step 4: upgrade based on new evidence

Find the matching draft, and the status upgrades to draft_confirmed.

Only clear evidence that nothing was created allows it to enter a retryable state.

Still can't confirm — keep it at unknown. Waiting isn't the system being incapable; it's preventing the loss from growing.

Why not "retry three times and call it done"

Network programs commonly auto-retry, but a publish action isn't an ordinary read. Reading the same page three times usually doesn't change the outside world; creating a draft or broadcasting three times does.

Whether an action can auto-retry depends on whether it has reliable idempotency: if the same run ID is submitted twice, can the platform or the local adapter guarantee only one result.

If the platform provides no such guarantee, the safest policy is submit once, record once, read back once. You can't carry an engineering framework's default of three retries into a publishing system.

What a publish receipt should be able to answer

I want any run to be able to answer, after the fact:

  • Who authorized what action.
  • Which version the content was at the time.
  • What the target account was.
  • When the request went out.
  • What ID the platform returned.
  • What was seen on read-back.
  • Which piece of evidence supports the final status.

The receipt shouldn't store full tokens, cookies or private keys. It records auditable facts, not a copy of sensitive credentials.

When duplicate drafts appear, don't bulk-delete immediately

Seeing two similar drafts, the first instinct is usually to clear one. But without comparing first, it's easy to delete the newer version.

A steadier order:

  1. Compare platform IDs.
  2. Compare creation and update times.
  3. Compare body versions or key digests.
  4. Confirm which one the later flow references.
  5. Back up the necessary content.
  6. Then perform a single, traceable delete.

A cleanup is also an external action, needing a clear target and a result read-back.

A small team is worth this gate too

Some think only big companies need a state machine and audit. An individual creator needs it more, because one person plays author, editor, operator and developer at once — most prone to mistaking "I know what I'm doing" for system control.

The gate doesn't require a complex backend. The minimal version can just be:

  • Two separate commands for draft and broadcast.
  • One frozen content digest.
  • One step to confirm the target account.
  • One platform read-back.
  • One "unknown means no retry" rule.

I don't measure publishing automation by whether it never errors, but by whether one timeout creates a second result. Check your current entrance first: if "save" and "send" are still hidden inside the same action, split them apart before you talk about going faster.

SOURCE AND VERIFICATION

Sources and limits

This records the control boundary of Kenton's own draft publisher; it doesn't claim the platform API has the same permissions on every account.

Verification: Based on the fail-closed and read-back flow of a local draft publisher.

ONE PRACTICAL NEXT STEP

Let's check your publish gate together

Send me how your publisher handles a timeout and whether 'save' and 'send' are the same action behind one button. If a timeout can quietly create a second result, that's the thing to fix first — I'm glad to walk the state machine through with you before you add any speed.

Message me
Read more articles ↗

KENTON / BUY & CONSULT

Subscriptions & software.

ChatGPT subscriptions · Zhike software
Buy directly, or talk it through first.

Opens the relevant platform, where you can view products and order or ask questions.