I once found two nearly identical articles published within minutes of each other.
Because I run several sites on autopilot as an indie developer, I let scheduled agents handle content generation and report aggregation. One day generation ran heavier than usual and a single run overran its window. Before it finished, the next trigger fired, a second agent started, produced another article on the same topic, and pushed it.
We tend to assume a scheduled job "runs once at a fixed time," but in reality it can "run when the time comes even if the previous run hasn't finished." This article lays out an idempotency design for agents that survives both overlap and retry.
Why double execution happens
There are two main causes. If you do not separate them, your fix will miss.
The first is overlap. When the previous run drags on and the next trigger time arrives before it ends, two runs proceed in parallel. Generative agents vary widely in duration depending on input data and model response time — a job that usually finishes in five minutes occasionally takes twenty. A fixed-interval schedule does not absorb that jitter.
The second is retry. Most unattended execution environments automatically re-run failed tasks. The catch is "how far did it get before failing?" If the network drops right before the push, after the article file is already written, the run is judged a "failure" — yet the file remains, and re-running the same work duplicates the artifact.
These two differ in nature. Overlap is a "two run at once" problem, prevented with a lock. Retry is a "runs twice with a gap" problem, absorbed by making the artifact idempotent. You need both.
Prevent overlap with flock
The most reliable way to prevent overlap is to take an exclusive lock at the entry point of the run. Linux flock provides an advisory lock on a file descriptor and makes "give up immediately if the lock can't be taken" easy to express.
#!/usr/bin/env bash
# run_agent_guarded.sh
# If the same task is already running, skip this time
set -euo pipefail
LOCK="/tmp/agent-content-antigravity.lock"
exec 9>"$LOCK"
if ! flock -n 9; then
echo "⏭ Previous run still in progress. Skipping this time ($(date '+%F %T'))"
exit 0
fi
# From here, single execution is guaranteed
echo "▶ Started $(date '+%F %T')"
# --- actual agent work ---
generate_and_push_article
echo "■ Finished $(date '+%F %T')"
# Descriptor 9 closes automatically at process exit, releasing the lockThe -n (non-blocking) on flock -n is the crux. If the lock can't be taken it returns an exit code without waiting, so an overlapping run quietly yields to the next cycle. By not building a wait queue, you avoid runs piling up like a snowball. Put the lock file somewhere shared across processes such as /tmp; the descriptor releases on process exit, so no explicit release is needed.
That stops overlap. But a lock alone does not stop retry duplication, because two runs separated in time can each acquire the lock.