Idempotency: The Quiet Requirement Behind Reliable Publishing
Reliable publishing depends on making every retry answer one question: is this new work, or another attempt to finish the same intended publication?
Idempotency: The Quiet Requirement Behind Reliable Publishing
A daily publishing run reaches the merge step, sends the request, and loses the response. Did the merge fail, or did it succeed just before the connection closed? The scheduler wakes the workflow again. Should it open another pull request, publish another file, or verify what already happened?
Those are not unusual edge cases. They are the normal uncertainties of automation: processes restart, networks time out, providers delay responses, and operators retry jobs. A reliable content system must survive that uncertainty without turning one editorial intent into two articles.
The governing property is idempotency. RFC 9110 defines an idempotent request as one whose intended effect after multiple identical requests is the same as after one request [1]. For autonomous publishing, the idea extends beyond HTTP: repeating a run for the same scheduled intent should converge on one canonical article, one change history, one publication decision, and one live URL.
Idempotency is not merely “check whether the slug exists.” It is an end-to-end contract that identifies intent, records durable outcomes, reconciles ambiguous responses, and permits retries only where their effect is known.
Identify the intent before doing the work
Every publishing run needs a stable identity created before research, drafting, or repository changes begin. The identity should describe the intended outcome, not a particular execution attempt.
For a daily campaign, a practical key might combine:
- the routine or campaign identifier;
- the scheduled publication date; and
- the assigned editorial-calendar entry.
media-001:2026-10-04:idempotency is a better identity than a random process ID. A restarted worker can reconstruct it and ask whether that publication already exists. Separate attempt identifiers can still record individual executions, but every attempt points back to the same publishing intent.
The distinction matters. Retrying an attempt should continue the same intent. Changing the topic, date, or publication target creates a new intent and therefore requires a new key. AWS describes this as distinguishing “same client request ID, different intent,” a condition services must reject or handle explicitly rather than silently treating as a harmless retry [2].
Bind the intent key to an immutable fingerprint of the important inputs: scheduled topic, expected slug, publication date, target repository, and base branch. If a retry presents the same key with a different fingerprint, stop. The system has found a conflict, not a duplicate.
Make duplicate detection semantic
A slug is a valuable uniqueness constraint, but it only detects one shape of duplication. Two runs can publish substantially the same article under different slugs. Conversely, a legitimate correction can update an existing slug without representing a second publication.
Check for duplication at several levels:
- Run record: Has this intent already reached a terminal state?
- Editorial calendar: Is another active run assigned to this date and topic?
- Repository: Does the expected content path or front-matter slug already exist?
- Pull requests: Is there already an open or merged change for the intent key, branch, or content path?
- Production: Does the canonical URL already serve the intended article?
- Meaning: Does the draft materially overlap an existing article even if its title and slug differ?
The first five checks can be deterministic. The last requires editorial judgment or a bounded similarity review. That review should compare thesis, audience promise, section structure, and key claims—not just matching phrases.
When a matching artifact is found, the workflow should reuse or reconcile it. It should not create a suffixed slug, second branch, or replacement pull request merely to make the command succeed.
Treat side effects as state transitions
An autonomous publisher performs several mutations: writing a file, creating a branch, committing, opening a pull request, merging, and updating the run record. Each mutation should be expressed as a transition from a known state to a known next state.
For example:
scheduled → researched → drafted → QA passed → validated → PR open → merged → live verified
Before performing a transition, read the authoritative system and confirm the current state. After requesting the transition, read it again and record the observed result. This turns retries into reconciliation:
- If the branch already exists at the expected commit, continue.
- If the article file exists with the expected fingerprint, validate it instead of rewriting it.
- If a pull request is already open for the branch, reuse it.
- If the pull request is merged, do not send another merge request; verify the merge commit and production.
- If production already serves the expected fingerprint, record verification rather than redeploying.
Git and GitHub provide durable identifiers that support this pattern: content paths, commit SHAs, branch refs, pull-request numbers, and merge commits. Store them on the run record as soon as they are confirmed. GitHub also recommends avoiding concurrent requests and pausing between mutative requests because rapid mutations can conflict or trigger rate limits [4]. A serial, state-aware publishing workflow is easier to reconcile than a burst of overlapping writes.
Use idempotency keys where APIs support them
Some APIs accept a caller-provided idempotency key. Stripe, for example, records the result associated with an idempotency key and returns that result for subsequent requests using the same key; it also compares parameters to prevent accidental reuse for a different operation [3]. AWS describes a similar client-token pattern for making retries safe after transient failures [2].
When a provider offers this contract:
- generate the key once per intended mutation;
- persist it before sending the request;
- reuse it for retries of that same mutation;
- never reuse it for different parameters or a new intent; and
- respect the provider's retention window and documented behavior.
An idempotency key is not a universal transaction. It usually protects one provider operation for a bounded period. It cannot by itself ensure that a Git commit, pull request, hosting deployment, and Paperclip run record all agree. The workflow still needs reconciliation across systems.
When an API does not support keys, use a natural unique identifier and a read-before-write contract. A deterministic branch name, article path, canonical slug, or marker in the pull-request body can let the workflow locate prior work safely.
Preserve the unknown state
The most dangerous failure is not an explicit rejection. It is an ambiguous outcome.
Suppose a merge request times out. Retrying immediately assumes it failed. Marking the run complete assumes it succeeded. Either assumption can be wrong. The correct state is not confirmed.
From that state, query GitHub for the pull request, merge status, head SHA, and resulting commit. Only then choose the next transition. RFC 9110 explains why idempotent methods can be retried after a lost response and cautions against automatically retrying non-idempotent requests unless the client knows the operation is idempotent or knows the original was not applied [1]. The same caution belongs in every publishing integration.
Use explicit outcome values such as:
passed— the requested state is confirmed;failed— the system definitively rejected or could not perform the transition;blocked— a named condition prevents safe continuation; andnot_confirmed— the request may have taken effect and must be reconciled.
Do not collapse not_confirmed into failed. That shortcut is how retry loops create duplicates.
Put uniqueness constraints near the data
Workflow logic is necessary, but it is vulnerable to races. Two workers can both check that no article exists and then both proceed. Protect critical invariants as close as possible to the system of record.
Useful constraints include:
- one active run per routine and scheduled date;
- one canonical article slug and content path;
- one active content branch per publishing intent;
- one pull request for that branch and target;
- one sitemap entry and one RSS item per canonical URL; and
- one terminal publication record per intent.
Git branch creation and pull-request lookup provide some practical protection. A content validator can reject duplicate slugs. The generated sitemap and feed can be tested for exactly one matching URL. The control plane can coalesce overlapping schedule ticks into the already active run.
Where a database coordinates mutations, record the idempotency key and resulting resource in the same atomic transaction when possible. AWS notes that recording the token separately from the mutation can leave either a token without its resource or a resource without its token [2]. The publishing equivalent is marking a run “PR created” before the PR number is durably known.
Retry narrowly and with limits
Idempotency makes retries safer; it does not make unlimited retries wise. A failing dependency can turn synchronized retries into more load and more uncertainty.
Classify operations before retrying:
- Read-only queries can usually retry with bounded exponential backoff and jitter.
- Idempotent mutations can retry with the same key after documented transient failures.
- Reconciled mutations require a read of authoritative state before another attempt.
- Non-idempotent or unclear operations must stop until their outcome can be determined.
Set attempt limits and deadlines. Record every attempt without inventing a new publishing intent. If the budget is exhausted, block the run with the last confirmed state, the ambiguous or failed operation, and the precise next action.
A retry should move the system toward a known state. If it only repeats uncertainty, it is not a recovery strategy.
Test retries as deliberately as the happy path
The relevant tests are interruption tests, not just “run the pipeline twice.” Simulate failures at each boundary:
- restart after the article file is written but before commit;
- lose the response after pushing the branch;
- retry after a pull request was created successfully;
- time out while requesting a merge;
- restart after merge but before the run record is updated;
- receive a new scheduler tick while the earlier run remains active; and
- present the same intent key with altered topic or slug inputs.
For every scenario, assert the invariant: one canonical article, one active change path, and one terminal publication record. Also assert that the system stops on conflicting intent rather than overwriting or improvising.
Production verification is part of the test. The article route, blog index, sitemap, and RSS should each contain exactly one expected canonical URL with the correct date and metadata. Idempotency is incomplete if repository state is clean but discovery surfaces expose duplicates.
Make the run record the reconciliation map
A durable run record should let an operator resume without guessing. At minimum, retain:
- publishing intent key and input fingerprint;
- scheduled date, topic, slug, and content path;
- artifact revision and QA result;
- branch, commit SHA, and pull-request number;
- merge result and merge commit;
- production URL and verification evidence;
- attempt history and any
not_confirmedtransition; and - terminal outcome with the responsible next owner when blocked.
The record does not replace GitHub, the repository, or production as sources of truth. It tells the recovery process where to look and what state it expects to find.
Reliability means convergence
The visible work of autonomous publishing is research, writing, editing, and release. Idempotency is the quieter structure that keeps all four from being repeated incorrectly.
Give each editorial intent a stable identity. Reject changed inputs under the same identity. Check for duplicates across the run record, repository, pull requests, production, and article meaning. Persist identifiers after every confirmed transition. Reconcile ambiguous responses before retrying. Enforce uniqueness close to the systems of record, and test restarts at every side-effect boundary.
The goal is not that a workflow never repeats. Real systems repeat constantly. The goal is that every safe repetition converges on the same approved outcome: one useful article, one traceable publication path, and no surprises for readers or operators.
Sources
- IETF, RFC 9110: HTTP Semantics — Idempotent Methods.
- Amazon Web Services, Making retries safe with idempotent APIs.
- Stripe, Idempotent requests.
- GitHub Docs, Best practices for using the REST API.