DEV Community

Cover image for A Missing Same-Day Guard Lets Two claude -p Cron Runs Duplicate Articles
soy
soy

Posted on Originally published at media.patentllm.org

A Missing Same-Day Guard Lets Two claude -p Cron Runs Duplicate Articles

A daily pipeline that runs claude -p headless via cron, with a separate manual invocation of the same task, exposed a same-day duplicate-write hazard live: save-article has no per-day-per-category uniqueness check, so two unrelated runs can each successfully write a different article to the same category on the same day. The pipeline's own agent noticed this exact hazard once before and could not act on it, because a headless run has no one to answer its own confirmation prompt.

How it works

The pipeline in question (scripts/agent_news.sh) runs claude -p under cron at 18:00 JST daily, with a fixed tool allowlist (Read Write Edit Bash WebFetch WebSearch Glob Grep), --permission-mode acceptEdits, and JSON output logged to logs/agent_news_YYYYMMDD.log. The task instructs the agent to fetch dozens of release feeds in parallel using background subagents, then save up to five category articles via python scripts/news_db.py save-article.

A first failure mode surfaced on 2026-09-15: the agent spawned 7 background subagents, but the default background-wait ceiling (600s) expired before they finished. One subagent was killed by the system, the run exited 0 having spent $4.19 and saved zero articles — a silent, billed no-op. The fix was raising the ceiling via CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=1800000 (30 minutes), set as an exported env var in the script itself so the limit is a deliberate stop, not an accident.

A second, different failure mode was caught live on 2026-09-27: a manual claude session ran the same daily procedure at the same time cron's own claude -p (PID 26854, started 18:00:01 JST) was independently doing the same thing. ps aux confirmed both processes coexisting more than 30 minutes in. Checking sqlite3 data/media.db ".schema articles" showed the only uniqueness constraint is UNIQUE(slug, content_type) — slugs are derived from the generated title, so two different picks for the same category on the same day get two different slugs and both insert cleanly. The pipeline's own agent hit this exact scenario on 2026-09-26 (visible in logs/agent_news_20260926.log), correctly identified the risk, proposed killing the competing process, and then had no mechanism to get an answer, since a headless -p run cannot pause for confirmation.

When this helps

This affects anyone running claude -p on a fixed schedule (cron, systemd timer) for a task that produces persisted, idempotency-assumed output — daily digests, reports, or scraped-and-saved content — where the same task can also be triggered ad hoc or manually. If the underlying save step has no natural key beyond a generated slug or filename, two overlapping runs will both succeed and both write, silently doubling output for that period.

It does not affect single-operator interactive sessions with no scheduled counterpart, or pipelines where the storage layer already enforces a (category, date)-style uniqueness constraint or checks existing rows before writing. It also does not affect the separate background-subagent timeout issue from 2026-09-15 — that one is about background wait limits inside a single run, not about two runs colliding.

Measured here

2026-09-27 18:31 JST時点の実測: ps auxでcron起動のclaude -p(PID 26854)が18:00:01起動から約31分経過してもまだ実行中であることを確認。sqlite3 data/media.db "select id, category, created_at from articles where date(created_at)=date('now')"で、対話セッション側が保存した5件(id 1065–1069、いずれもcreated_at 2026-09-27 09:29台UTC)のみが存在することを確認。2026-09-15の障害ログ(logs/agent_news_20260915.log.failed-1800)には、7個のサブエージェント起動・1個がシステムにkillされる・exit=0・total_cost_usd 4.192997900000001という実測値が記録されている。2026-09-26のログ(logs/agent_news_20260926.log)には、エージェント自身が並行実行を検知した旨のresultテキストと、total_cost_usd 6.929127000000001、spawned 5・completed 5・failed 0という実測値が記録されている。

Is it worth it

The 2026-09-15 fix (raising the background-wait ceiling) was the right response to that specific failure, but it does not address this second, separate hazard. The gap here is architectural, not a matter of remembering not to run the script manually near 18:00: save-article and save-featured should check for an existing row with the same (category, date) before doing any research, or the articles table should carry a partial unique index on (category, date(created_at), content_type) where content_type is the news-digest type, so a second concurrent run fails fast at the write instead of silently succeeding.

Until that guard exists, treat any manual run of this pipeline as unsafe within the cron's daily window — check ps aux for a running agent_news.sh/claude -p process first, or hold the manual run until the scheduled one has logged its result. This is a five-minute check that fully avoids the failure mode observed here.

Written while running Claude Code unattended in production every day. Full archive: https://media.patentllm.org

Top comments (0)