CLAUDE LABJP
SCIENCE — Claude Science launches in beta, a workbench that unifies research tools and produces auditable artifactsMODEL — Fast mode for Claude Opus 4.7 retires on July 24; migrate to Opus 4.8 fast modeCODE — Claude Code v2.1.195 adds a toggle to disable mouse clicks in fullscreen modeCODE — Hyphenated hook matchers now match exactly instead of substring-matchingAGENT — Claude Science pairs a coordinating agent with specialists and a reviewer that checks citations and mathCLOUD — Claude is generally available in Microsoft Foundry on Azure with Azure-native accessSCIENCE — Claude Science launches in beta, a workbench that unifies research tools and produces auditable artifactsMODEL — Fast mode for Claude Opus 4.7 retires on July 24; migrate to Opus 4.8 fast modeCODE — Claude Code v2.1.195 adds a toggle to disable mouse clicks in fullscreen modeCODE — Hyphenated hook matchers now match exactly instead of substring-matchingAGENT — Claude Science pairs a coordinating agent with specialists and a reviewer that checks citations and mathCLOUD — Claude is generally available in Microsoft Foundry on Azure with Azure-native access
Articles/Cowork
Cowork/2026-07-01Advanced

Let the Downstream Task Verify the Upstream Actually Ran Today: A Completion Ledger and Dependency Barrier for Unattended Schedulers

Unattended schedulers have no notion of dependencies, so when a morning data-refresh task fails silently, the noon generation task keeps running on yesterday's leftovers. This is a design for recording upstream completion atomically and having downstream assert its preconditions before running, with working TypeScript and lessons from my own operations.

Cowork31Scheduled Tasks8Automation33DependenciesReliability5

Premium Article

One morning, the 7 a.m. reference-data refresh task died partway through on a brief network blip. Nothing unusual landed in the logs; yesterday's file simply stayed in place. The real damage started next. The noon article-generation task read that reference data without complaint, and produced a "new" article from the same news as the day before, then published it. No one noticed an error, and only staler and staler output kept piling up. This is the quietest and most insidious shape of failure in unattended scheduling.

The root of this incident is not a bug in the code. It is a structural problem: most schedulers have no concept of "run task B only after task A finishes." Each task simply fires independently at its appointed time, and the downstream has no idea whether the upstream truly ran today. So the downstream starts on an implicit assumption that "it probably ran." This article replaces that implicit assumption with an explicit check, using two parts: a completion ledger and a dependency barrier.

Why "it probably ran" is dangerous — the overlooked third state

When we reason about dependencies by hand, we tend to think in two values: the upstream either "succeeded" or "failed." But in unattended operation, the state that really bites is a third one that most implementations drop.

StateMeaningWhat downstream should do
not-runUpstream hasn't run today, or died partwayHalt (do not produce degraded output)
ran-emptyUpstream ran fine but legitimately produced nothing todaySkip (this is not an error)
ran-producedUpstream ran and produced the artifact downstream expectsProceed

The trouble is that many implementations cannot tell "ran-empty" apart from "not-run." If the downstream only checks "does the reference file exist," yesterday's leftover exists, so it mistakes "not-run" for "ran-produced." Conversely, if it only checks "was the file updated today," it mistakes a day when the upstream legitimately returned nothing (say, a day with no new news) for an anomaly and fires needless alerts. Cleanly separating the three states is where every dependency barrier begins.

Running automated publishing across several sites, I once treated a legitimate "empty" as an anomaly and halted the downstream, which left the whole pipeline spinning uselessly on what should have been a quiet holiday. The cause was that the upstream's intent — returning empty was a normal outcome — never reached the downstream. So the ledger needs vocabulary that can express an "intended empty," not just success and failure.

The ledger data structure

The heart of a dependency barrier is a small ledger where each task records the outcome of its daily run. You do not need a heavyweight job queue; an append-style JSON file with one record per line does the job. First, pin the vocabulary down with types.

// ledger.ts
export type RunStatus = "ok" | "empty" | "error";
 
export interface RunRecord {
  task: string;            // e.g. "daily-reference-and-ticker"
  date: string;            // JST YYYY-MM-DD (the source of truth for day boundaries)
  status: RunStatus;       // ok=produced / empty=legitimate empty / error=failed
  fingerprint: string | null; // content hash of the artifact; null for empty/error
  artifactPath: string | null;
  finishedAt: string;      // ISO8601 (for auditing)
  note?: string;           // human-readable reason for empty or failure
}

Keeping status and fingerprint as separate fields is the key. status: "empty" means "the upstream met its responsibility, but there was legitimately nothing to produce today," and it carries fingerprint: null. That structurally distinguishes "ran-empty" from "not-run" (where no record exists at all). I fix date to JST because handling it in UTC shifts the day boundary and can make yesterday's record look like today's. That is a spot I've been burned by before, so I always generate dates with an explicit timezone.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Why confusing 'did not run', 'ran but was legitimately empty', and 'ran and produced output' silently degrades an unattended pipeline, and a ledger design that keeps the three states apart
How to record upstream completion atomically and have downstream assert it before running, implemented in working TypeScript with fingerprint matching
The 'never trust the upstream' operating decisions I've settled on running multi-site automated publishing on a scheduler with no DAG
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Cowork2026-06-29
Failing Loud on Stale Inputs: A Freshness Contract for Unattended Pipelines
How to stop a scheduled, unattended pipeline from silently shipping degraded work when its upstream data is empty or stale. We implement a freshness contract in bash that asserts recency, non-emptiness, and provenance, plus two real pitfalls I hit running Cowork scheduled tasks.
Cowork2026-06-13
Running Cowork Scheduled Tasks in Practice — From a Morning Digest to Unattended Weekly Reports
How to set up recurring runs, reminders, and automated reports with Claude Cowork's scheduled tasks — covering cron basics, prompt design that survives unattended execution, and how to schedule multiple tasks so they stay reliable.
Cowork2026-05-03
Honest 6-Month Review of Claude Cowork — What Actually Worked for an Indie Developer
A candid 6-month review of Claude Cowork from an indie developer's perspective. Covers scheduled tasks, skills, and the memory system — what exceeded expectations, what was genuinely painful, and the hard-won lessons that changed how I use AI automation.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →