◉CLAUDE LABJP
●2.1.289 — Claude Code 2.1.289 (Oct 3) fixes deny/ask rules on nested parts of compound shell commands, a terminal freeze, and Read deny through symlinks●10/07 — 1 day left until the old spellings of Claude Desktop / Cowork managed-config keys stop being accepted. After the cutoff they fail closed●ACCOUNTS — A request to switch between multiple accounts in Claude Desktop has gathered over 200 comments on GitHub. How far today's workarounds go is the real question●NEW — When Claude Code committed despite a no-commit instruction, I separated the cause with a deny rule and a PreToolUse hook●COMPARE — A Zenn post compares Opus 5.5 and Sonnet 5.5 with numbers. The real question is where to draw the line per job●SONNET5.5 — Code written for Sonnet 5 can break on 5.5 in five ways. Thinking settings and forced tool_choice are the places to check first●2.1.289 — Claude Code 2.1.289 (Oct 3) fixes deny/ask rules on nested parts of compound shell commands, a terminal freeze, and Read deny through symlinks●10/07 — 1 day left until the old spellings of Claude Desktop / Cowork managed-config keys stop being accepted. After the cutoff they fail closed●ACCOUNTS — A request to switch between multiple accounts in Claude Desktop has gathered over 200 comments on GitHub. How far today's workarounds go is the real question●NEW — When Claude Code committed despite a no-commit instruction, I separated the cause with a deny rule and a PreToolUse hook●COMPARE — A Zenn post compares Opus 5.5 and Sonnet 5.5 with numbers. The real question is where to draw the line per job●SONNET5.5 — Code written for Sonnet 5 can break on 5.5 in five ways. Thinking settings and forced tool_choice are the places to check first
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.

Cowork43Scheduled Tasks13Automation45DependenciesReliability5

✦ 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 $15 for lifetime access
View Membership →

Related Articles

◈ Cowork2026-09-18
How I decide between Cowork and Claude Code: by where approvals land
Cowork and Claude Code run the same Claude, so I stopped comparing features and started sorting work by one question: can a human approval interrupt this step, or not? Here is the line I draw and the code I keep on the unattended side.
◈ Cowork2026-10-01
Before You Trim a Scheduled Task's Runbook, Count How Many Times It Gets Re-read
I cut 13 KB from a scheduled task's preloaded docs, and recounted by re-reads it was worth about 1.3 tool calls. Here is the ledger that counts re-reads, and a shell helper that records bytes during a run.
◈ Cowork2026-09-24
Will Your Cowork Scheduled Task Still Run Next Saturday?
A definition that runs once and stops, two fire times packed into one task, a weekday that quietly emptied after an edit. Cowork schedules can go missing without leaving a single log line. Here is a working audit that compares what you defined with what you meant.
📚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