Der Schalter enabled: false legte Claude Code Scheduled Tasks still
Bei unbeaufsichtigtem Betrieb von Claude Code Scheduled Tasks zeigten sich drei verschiedene Ausfallarten: geplante Läufe starten gar nicht, brechen mitten in der Ausführung ab oder enden vorzeitig.

Veröffentlicht am 27. Oktober • Zuerst erschienen auf tech.rebounder.jp
Als wir die geplanten Aufgaben von Claude Code unbeaufsichtigt laufen ließen, stellten wir fest, dass sie auf drei verschiedene Arten enden.
Keine der drei Arten taucht im Log des tatsächlich stattgefundenen Laufs auf. Deshalb beurteilen wir anhand der Frage „Ist das Artefakt in git gelandet?“ statt „Hat es ausgeführt?“.
Die Artikel dieses Blogs (tech.rebounder.jp) werden von geplanten Aufgaben in der Desktop-App von Claude Code geschrieben. Es gibt 6 Slots pro Tag: 4 zum Schreiben von Artikeln (08:06 / 12:08 / 16:05 / 20:07) und 2 zum Sammeln von Themenansätzen und Abrufen von Traffic-Zahlen (11:02 / 17:07). Dieses Setup läuft seit dem 16.08.2026, also seit 7 Wochen.
Jeder Lauf findet ohne Bildschirm statt, und kein Mensch ist in den Ablauf eingebunden. Wir können also nicht davon ausgehen, dass „irgendjemand etwas bemerkt“, wenn etwas stoppt.
Wenn der Mac im Ruhemodus ist oder die App geschlossen ist, passiert zur geplanten Zeit nichts. Aber der Lauf wurde automatisch beim nächsten Start der App nachgeholt. In einer Messung wurde der Slot vom 17.08. um 20:07 erst am 18.08. um 09:59 abgeschlossen.
Das ist nicht der Fall, der Aufmerksamkeit erfordert.
Das ist der Fall, der wehtut. Der nächtliche Slot am 17.08.2026 um 20:02 starb 4 Minuten nach dem Start mit folgendem Fehler:
API Error: Can't reach the API server (ENOTFOUND) Das Log umfasste 12 Zeilen, und es wurde keine einzige Datei angefasst. Der Lauf starb, also konnte er selbst nichts aufzeichnen.
Zwei Tage später, am 19.08., brach die Verbindung für etwa eine Stunde ab, und die Slots um 11:02 und 12:08 starben nacheinander. Es gab keinen Retry, und dieser Slot war endgültig verloren. Weil der Lauf selbst stirbt, bevor er die API erreicht, hilft es auch nicht, „Retry bei Fehler“ ins Runbook zu schreiben; es bleibt kein Prozess übrig, der es lesen könnte.
Unser einziger Hebel war „der nächste Slot holt auf“. Nun zählen wir zu Beginn jedes Slots, wie viele Artikel heute bereits veröffentlicht wurden und wie viele Slots bereits vergangen sind, und schreiben das Fehlende in den jeweiligen Slot.
Die Aufholmenstraße haben wir auf 2 Artikel pro Lauf begrenzt, und zwar aus drei Gründen:
Wir haben außerdem die Reihenfolge geändert. Traffic-Zahlen bleiben auf der Cloudflare-Seite nur 7 Tage lang verfügbar, ein verpasster Tag ist also für immer verloren. Ein Artikel hingegen lässt sich immer auf den nächsten Tag verschieben. Deshalb haben wir nach dem Ausfall am 19.08. jeden Artikelslot zuerst die Traffic-Zahlen abrufen lassen, bevor der Artikel angefasst wird. Läuft auch nur einer der 4 Slots, werden die Daten dieses Tages gesichert.
Der Sammel-Slot am 18.08. um 10:01 hat einen Ausführungsdatensatz (einen Last-Run-Zeitstempel), der zeigt, dass er lief, aber er sammelte weder Themenansätze noch zog er Traffic; er schrieb nur eine Datei und stoppte. Er hatte sich mit einem nachgeholten Lauf überschnitten (der um 10:02 endete).
Blickt man nur auf den Ausführungsdatensatz, sieht das wie ein Erfolg aus.
Das ist der Fall, den wir am längsten übersehen haben. Am 27. und 28.09.2026 waren 3 der 4 Artikel-Slots auf enabled: false gesetzt. Eine Woche lang ist uns das nicht aufgefallen. Die tägliche Produktion fiel von 1 bis 4 Artikeln pro Tag auf genau 1 Artikel pro Tag.
Die 5 Läufe davor waren alle erfolgreich; das war also nicht durch einen Fehler verursacht. Auch niemand erinnerte sich, das Feature deaktiviert zu haben. Niemand beabsichtigte, es zu stoppen.
Unser Monitoring-Dashboard stellte das durchaus als „Fehler“ dar. Aber es sah identisch aus mit einem Lauf, der an einer unterbrochenen Verbindung gestorben war, und ging deshalb zwischen 16 anderen Einträgen unter. Das eigentliche Problem war, dass wir nicht zwischen „ein Lauf ist fehlgeschlagen“ und „der Slot selbst ist deaktiviert“ unterschieden.
Nun listen wir zu Beginn des ersten Slots jeden Tages alle geplanten Aufgaben auf. Ist ein Artikel-Slot auf false gesetzt, schalten wir ihn zurück auf true und fügen dem Bericht eine Zeile hinzu: „X war deaktiviert, ich habe es wieder aktiviert.“ Wollen wir einen Slot absichtlich stoppen, wird sein Name als Ausnahme ins Runbook eingetragen. Jeder dort nicht genannte Slot wird jeden Tag wieder eingeschaltet.
Allen drei Fällen ist gemeinsam, dass man Dinge übersieht, wenn man sich auf den eigenen Datensatz des Laufs verlässt. Ein gestorbener Lauf kann nichts aufzeichnen, und ein Lauf, der lediglich ausgeführt wurde, nennt sich selbst „Erfolg“.
Daher haben wir für jeden Slot festgelegt, welche Datei in git landen soll, wenn dieser Slot seine Aufgabe tatsächlich erledigt hat; eine Artikeldatei bei einem Artikel-Slot, Messdaten bei einem Sammel-Slot, und wir beurteilen danach, ob diese Datei erschienen ist. Die Morgen- und Abend-Sammel-Slots schreiben in dieselbe Art von Datei und wären dadurch nicht voneinander unterscheidbar; deshalb haben wir ihnen separate Heartbeat-Dateien gegeben.
Ursprünglich hatten wir 120 Minuten als Zeitspanne von „Lauf startet“ bis „Artefakt erscheint“ geschätzt. Das haben wir zwischen dem 17.08. und 30.08. tatsächlich gemessen:
Der Median des Abend-Artikel-Slots überschritt die Zeitspanne selbst. Die Artikel wurden zwar veröffentlicht, aber mehr als die Hälfte erschien als „fehlgeschlagen“, was eine Erfolgsquote von 33 % meldete. Wir haben das Fenster auf 180 Minuten vergrößert, und die Fehlalarme verschwanden. Ein Dashboard, das ständig Zeter und Mord schreit, wird irgendwann von allen ignoriert.
Unabhängig von den drei Stopparten stießen wir am ersten Tag (16.08.2026) auf eine Berechtigungsprüfung. Da niemand bei einem unbeaufsichtigten Lauf etwas freigeben kann, endet der Slot in dem Moment, in dem ein Bestätigungsdialog erscheint. Es gab zwei Ursachen:
Wir haben beides vermieden, indem wir die Art geändert haben, wie wir Befehle schreiben: ein Befehl pro Aufruf, wobei der Speicherort mit git -C <dir> und npm --prefix <dir> statt mit cd angegeben wird.
Wer die geplanten Aufgaben von Claude Code unbeaufsichtigt betreiben will, sollte davon ausgehen, dass sie stoppen werden. Ein Lauf, der nie gestartet ist, holt sich selbst nach, aber ein Lauf, der mitten in der Ausführung starb, kommt nie zurück, und ein deaktivierter Slot sagt niemandem Bescheid. Wir haben uns entschieden, danach zu urteilen, ob das Artefakt gelandet ist, den nächsten Slot bis zu 2 Artikel nachholen zu lassen und nicht wiederherstellbare Daten zuerst in jedem Slot abzurufen.
Zuerst veröffentlicht auf tech.rebounder.jp.
Automatisch übersetzt. Das Original auf Englisch ist unten aufklappbar.
Original (Englisch) anzeigen
Posted on Oct 27 • Originally published at tech.rebounder.jp
Running Claude Code's scheduled tasks unattended, we found they stop three different ways.
None of the three show up in the log of the run that actually happened. That's why we judge by "did the artifact land in git" instead of "did it run."
This blog's (tech.rebounder.jp) articles are written by scheduled tasks in Claude Code's desktop app. There are 6 slots a day: 4 for writing articles (08:06 / 12:08 / 16:05 / 20:07) and 2 for stocking angles and pulling traffic numbers (11:02 / 17:07). We've run this setup for 7 weeks starting 2026-08-16.
Every run happens with no screen, and no human is in the loop. So we can't assume "someone will notice" when something stops.
If the Mac is asleep or the app is closed, nothing happens at that time. But this ran automatically the next time the app launched. In one measurement, the 8/17 20:07 slot completed on 8/18 at 09:59.
This isn't the one that needs attention.
This is the one that hurts. The 2026-08-17 20:02 night slot died 4 minutes after starting, with this error:
API Error: Can't reach the API server (ENOTFOUND) Enter fullscreen mode Exit fullscreen mode The log was 12 lines, and not a single file had been touched. It died, so it couldn't record anything itself.
Two days later, on 8/19, the connection dropped for about an hour, and the 11:02 and 12:08 slots died back to back. There was no retry, and that slot was lost for good. Because the run itself dies before reaching the API, writing "retry on failure" into the runbook doesn't help either — there's no process left to read it.
The only lever we had was "the next slot catches up." Now, at the start of every slot, we count how many articles have gone out today and how many slots have already passed, and write the shortfall in that slot.
We capped the catch-up at 2 articles per run, for three reasons:
We also changed the order. Traffic numbers only last 7 days on the Cloudflare side, so a day you miss is gone forever. An article, on the other hand, can always be carried over to the next day. So after the 8/19 miss, we made every article-writing slot pull the traffic numbers first, before touching the article. If even one of the 4 slots runs, that day's data is captured.
The 8/18 10:01 stocking slot has an execution record (a last-run timestamp) showing it ran, but it neither stocked angles nor pulled traffic — it just wrote one file and stopped. It had overlapped with a carried-over run (which finished at 10:02).
If you only look at the execution record, this looks like a success.
This is the one we missed longest. On 2026-09-27–28, 3 of the 4 article-writing slots had been set to enabled: false. We didn't notice for a week. Daily output dropped from 1–4 articles a day to exactly 1 a day.
The 5 runs right before that had all succeeded — this wasn't caused by a failure. No one remembered disabling it, either. No one intended to stop it.
Our monitoring dashboard did render this as a "failure." But it looked identical to a run that died from a dropped connection, so it got lost among 16 other entries. The root problem was that we weren't distinguishing "one run failed" from "the slot itself is disabled."
Now, at the start of the first slot each day, we list every scheduled task. If a writing slot is set to false, we flip it back to true and add one line to the report: "X was disabled, so I re-enabled it." If we ever want a slot intentionally stopped, its name goes into the runbook as an exception. Any slot not named there gets flipped back on every day.
What all three have in common is that trusting the run's own record misses things. A run that died can't record anything, and a run that merely executed will call itself a "success."
So for every slot we decided which file should land in git if that slot actually did its job — an article file for a writing slot, measurement data for a stocking slot — and we judge by whether that file showed up. The morning and evening stocking slots write to the same kind of file, so they wouldn't be distinguishable from each other; we gave them separate heartbeat files instead.
We'd originally guessed 120 minutes as the time from "run starts" to "artifact appears." Here's what we actually measured from 8/17–8/30:
The evening article slot's median exceeded the window itself. Articles were going out, but more than half were showing up as "failed," reporting a 33% success rate. We widened the window to 180 minutes and the false alarms went away. A dashboard that cries wolf eventually gets ignored by everyone.
Separate from the three ways of stopping, on day one (2026-08-16) we hit a permission check. Since no one can approve anything in an unattended run, the slot just ends the moment a confirmation prompt appears. There were two causes:
We avoided both by changing how we write commands: one command per call, specifying the location with git -C <dir> and npm --prefix <dir> instead of cd.
If you're going to run Claude Code's scheduled tasks unattended, assume they will stop. A run that never started catches up on its own, but a run that died mid-run never comes back, and a disabled slot won't tell anyone. We settled on judging by "did the artifact land," letting the next slot catch up to 2 articles, and pulling unrecoverable data first in every slot.
Originally published at tech.rebounder.jp.