Geplande taken

De job staat op groen en doet niets

Een nachtelijke bouwpijplijn stond zeventien dagen stil terwijl elke controle op groen bleef. Niet omdat er geen bewaking was, maar omdat de bewaking naar de verkeerde vraag keek: draaide hij? — in plaats van: kwam er iets uit?

De storing zat bovenstrooms. Een stap die materiaal aanleverde brak op een gewijzigde paginastructuur en faalde veertig keer achter elkaar. De bouwer eronder vond daardoor elke nacht niets te doen en meldde dat netjes: [SKIP] niets te verwerken. Dat woord stond in het succespatroon van de bewaking — terecht, want een overslag is normaal gesproken een geldige uitkomst. En de aanleverende stap zelf stond in geen enkele controle. Twee gaten die elkaar precies afdekten.

Dit is geen zeldzaam ongeluk maar een structurele eigenschap van geplande taken: ze draaien als er niemand kijkt, en hun normale toestand is stilte. Een webserver die omvalt, merk je binnen een minuut omdat iemand een pagina opvraagt. Een taak die om vier uur 's nachts niets doet, lijkt op een taak die om vier uur 's nachts zijn werk doet.

Het probleem heeft een naam in de literatuur. Onderzoekers van Microsoft doopten het gray failure: storingen waarvan de verschijningsvorm te subtiel is voor een snelle, harde vaststelling. De kern ervan noemen ze differentiële waarneembaarheid — de foutdetectie van een systeem ziet niets, terwijl wat ervan afhangt wél last heeft. Het klassieke foutmodel waarin een onderdeel óf werkt óf gestopt is, dekt dat niet, en dat is precies het model waarop de meeste taakbewaking gebouwd wordt.

Hieronder: negen bewakingslagen, twaalf manieren waarop een geplande taak stil kapotgaat, en een scan die zegt welke van die twaalf jouw huidige opzet vandaag zou missen. Daarna twee werkbanken voor de twee valkuilen die dit incident veroorzaakten — het succespatroon en de exit-code — en tot slot waar het vakgebied dit allemaal al had opgeschreven.


1 — Dekkingsscan

Wat zou jouw bewaking missen?

Vink aan wat je vandaag daadwerkelijk controleert. De twaalf storingen hieronder worden er dan langs gelegd: welke geeft een signaal, welke blijft onzichtbaar, en hoe lang duurt het voor je het weet.

Twee dingen die de snelheid bepalen

storingen leveren een signaal op
blijven onzichtbaar tot je iets mist
traagste detectie van wat je wél vangt

De twaalf storingen

Gesorteerd: onzichtbaar eerst, daarna traagste detectie.

Waarom de opbrengst-laag zoveel vangt, en waarom dat toch niet genoeg is. Van de twaalf storingen laat er negen een spoor na in de opbrengst: als je kijkt of er de laatste dagen echt iets geproduceerd is, vang je bijna alles. Alleen: die laag is traag. Met een drempel van drie dagen weet je het pas na drie dagen, en bij twee storingen — een lege bron die een goed bestand overschrijft, en een alarm dat niet aankomt — zwijgt hij helemaal. De snelle lagen zijn smal, de brede laag is traag. Daarom heb je er meer dan één nodig, en niet negen.
En waarom een externe hartslag óók niet het hele antwoord is. Het aantrekkelijkste enkele antwoord op deze pagina is de laatste laag: laat je taak na afloop een externe dienst pingen en laat die alarmeren als de ping uitblijft. Dat vangt in het model hierboven zes van de twaalf storingen en het is de snelste laag van allemaal, want er hoeft niemand te wachten op de volgende controleronde. Maar het is een detector van afwezigheid, en de gemeenste storingen zijn juist aanwezig: een taak die keurig doorloopt, netjes pingt en niets nuttigs doet, ziet er voor een hartslag-dienst identiek uit aan een geslaagde run. De auteurs van het gray-failure-artikel maken precies dat punt — ze pleiten voor gezondheidsmeting langs meerdere assen in plaats van één signaal.

2 — Werkbank

Het succespatroon dat ook op mislukking past

De goedkoopste controle op een geplande taak is een grep op het logbestand: staat de regel erin die er bij een geslaagde run in hoort? Het probleem is dat "geslaagd" en "niets gedaan" in de meeste logs op elkaar lijken, en dat het patroon meestal wordt geschreven door iemand die naar een geslaagd log kijkt — niet naar een log van een run die stilviel.

Hieronder acht regels uit hetzelfde logbestand. Drie horen bij een echt geslaagde run, vier bij een run die niets nuttigs deed, één bij een openlijke fout. Pas het patroon aan en kijk wat er groen kleurt.

De regel die hieruit volgt. Een succespatroon moet matchen op een getal dat groter dan nul is, niet op een woord. klaar is een bewering van het script over zichzelf; 3 items verwerkt is een waarneming. Schrijf je logregel zó dat het aantal erin staat, en laat het patroon daarop matchen.
En de moeilijkheid die daaronder ligt. Probeer de vierde knop en dan de derde: een streng patroon dat alleen op verwerkte aantallen matcht, slaat één regel ten onrechte af — de run die niets deed omdat er echt niets te doen wás. Dat is een geldige uitkomst en hoort geen alarm te geven. Je hebt dus twee soorten "overgeslagen" en die moeten in het log verschillend leesbaar zijn: er was niets, en dat klopt tegenover er was niets, en dat is verdacht. De meeste scripts loggen die twee met dezelfde zin, en dan is er geen patroon dat het onderscheid kan maken — niet omdat het patroon slecht is, maar omdat de informatie er niet in staat. De reparatie zit dus in het script, niet in de bewaking.

3 — Werkbank

Wat de exit-code wel en niet zegt

De tweede controle die iedereen bouwt: kijk of de laatste run met status nul eindigde. Dat lijkt hard — het is een getal uit het besturingssysteem, geen tekst uit een log. Maar de exit-status van een shell-script is standaard die van het laatst uitgevoerde commando, en dat is bijna nooit het commando dat het werk deed.

Hieronder een script van vier stappen. Zet stappen op "faalt" en schakel de shell-opties aan om te zien wat de scheduler uiteindelijk te zien krijgt.

0exit-status
Zo ziet het script eruit met deze instellingen:
De valkuil die de meeste mensen twee keer maken. set -e is de standaardaanbeveling, en hij werkt — maar hij zorgt er ook voor dat het script stopt vóórdat de regel draait die jou een melding stuurt. Je bent dan strenger geworden én stiller. De oplossing is een trap die bij elke afsluiting vuurt, ongeacht de reden, en die de status doorgeeft die het script had.
set -euo pipefail

melden() {
  status=$?
  curl -fsS -m 20 --retry 3 -d "job klaar met status $status" "$MELDKANAAL" \
    || echo "$(date '+%F %T') MELDING NIET AFGELEVERD (status $status)" >> "$LOG"
  exit "$status"
}
trap melden EXIT

Drie dingen tegelijk: de melding vuurt ook bij een afgebroken run, een mislukte verzending wordt zelf vastgelegd in plaats van stil te verdwijnen, en de oorspronkelijke status blijft behouden in plaats van te worden overschreven door die van curl.

Vijf plekken waar set -e niet ingrijpt — plus twee die niemand ziet aankomen

De foutafbreking is geen algemene garantie maar een regel met uitzonderingen, en die staan letterlijk in de handleiding van de shell. Hij grijpt niet in bij een commando in de conditie van een if, in de lus-conditie van while of until, bij elk commando in een &&- of ||-keten behalve het allerlaatste, bij elk commando in een pijplijn behalve het laatste, en bij een commando waarvan de status met ! wordt omgedraaid.

Twee gevolgen daarvan zijn nauwelijks bekend en allebei goed voor een verloren avond:

  • Roep je een functie aan in een if-conditie, dan is de foutafbreking uitgeschakeld in de hele functiebody. Niet alleen voor de aanroep — voor alles wat die functie vanbinnen doet. De functie loopt na een fout gewoon door tot het einde.
  • local v=$(commando) maskeert een fout volledig. De toekenning zelf slaagt namelijk altijd, dus de status van het commando verdwijnt. Dezelfde regel zónder local, op het hoogste niveau, geeft de status wél door. Dat verschil is onzichtbaar bij het lezen.

Praktische conclusie: set -euo pipefail is een goede standaard en een slechte garantie. Wil je zeker weten dat een stap slaagde, controleer die status dan expliciet in plaats van erop te vertrouwen dat de shell het voor je doet.

Wat de scheduler zelf van de laatste run bewaart

Naast je eigen logbestand houdt de scheduler een status bij, en die is vaak makkelijker uit te lezen dan een log te ontleden. Op macOS toont de takenlijst per taak de laatste exit-status. Staat daar een negatief getal, dan is dat geen exit-code maar het signaal dat de taak beëindigde: -15 betekent dat hij een afsluitverzoek kreeg, -9 dat hij hardhandig is gestopt. Dat onderscheid is het lezen waard, want een taak die wordt afgeschoten heeft een heel ander probleem dan een taak die zelf besluit te stoppen.

Die lijstweergave is inmiddels als verouderd gemarkeerd; het aanbevolen alternatief geeft per taak ook het aantal runs en de laatste exit-code als losse velden, wat een stuk minder foutgevoelig is om te ontleden.

Kanttekening voor wie een taak handmatig wil testen via de scheduler: de eenvoudigste manier om dat te doen op macOS start de taak opnieuw zodra hij klaar is — ook bij succes, niet alleen bij falen — met een wachttijd van tien seconden ertussen. Test je zo een taak die een melding stuurt, dan krijg je er drie voor je doorhebt wat er gebeurt. Verwijder de testtaak expliciet als je klaar bent.


4 — Achtergrond

Vier manieren waarop een taak stil kan falen

De storingen hierboven zijn schedulerneutraal. Deze vier zijn dat niet: het zijn faalpaden waarbij de taak vanuit je terminal perfect werkt en vanuit de scheduler niet, zónder dat er ergens een foutmelding verschijnt. Ze zijn hier op macOS beschreven, maar de eerste drie hebben een tegenhanger op elk platform.


5 — Herkomst

Dit is allemaal al eens opgeschreven

Niets hierboven is nieuw. Wie geplande taken bewaakt, komt bij dezelfde vier ideeën uit als het vakgebied dat monitoring professioneel bedrijft — alleen heten ze daar anders, en het is nuttig om die namen te kennen als je verder wilt lezen.

Alarmeer op afwezigheid, niet op een foutmelding

Een taak die niet draait, stuurt geen foutmelding — dat is de definitie van het probleem. De oplossing is het signaal omdraaien: laat de taak bij succes iets versturen, en alarmeer als dat uitblijft. In de spoorwegtechniek heet dat een dodemansknop, en de diensten die dit voor geplande taken aanbieden gebruiken die term letterlijk. Andere namen voor hetzelfde: hartslag-bewaking, waakhond-alarm, baken-alarm.

De vendorneutrale variant staat in de aanbevelingen van Prometheus, onder het kopje batchtaken: alarmeer als de taak niet recent genoeg gelukt is, met een drempel van minstens twee volledige runs. Voor een taak die elke vier uur draait en een uur duurt, is tien uur een redelijke grens. Kun je één mislukte run niet hebben, laat de taak dan vaker draaien — één keer falen hoort geen mens wakker te maken.

Versheid als eerste klasse controle

De "is het log recent geschreven"-laag is geen huisvlijt maar een gevestigd patroon: freshness checking. Het is bedoeld voor precies deze situatie — een controle die niet zelf actief kan meten, maar afhankelijk is van iets dat op gezette tijden een resultaat komt afleveren. Is het laatste resultaat ouder dan de drempel, dan geldt het als muf en gaat er alsnog een alarm af.

De documentatie van Nagios gebruikt als voorbeeld letterlijk een nachtelijke back-uptaak, met een drempel van 93.600 seconden — zesentwintig uur, oftewel een etmaal plus twee uur speling — en een terugvalcommando dat meldt dat er over de back-up niets gerapporteerd is. Dat die drempel in de praktijk telkens weer op ongeveer 26 uur uitkomt, is geen toeval: het is precies de intervallengte plus genoeg marge voor een late start.

De bewaking is zelf een systeem dat kan omvallen

Voor "wie bewaakt de bewaker" bestaat een term: meta-monitoring. Grafana omschrijft het als het bewaken van je bewakingssysteem, met een alarm wanneer dat niet werkt zoals het hoort. De aanbevelingen van Prometheus voegen er een verstandige nuance aan toe: liever één test die de hele keten doorloopt — van meting via alarmering tot het bericht dat aankomt — dan een los alarm op elk onderdeel afzonderlijk. Precies daarom staat in de scan hierboven "komt het alarm zelf aan" als eigen laag: dat is de ketentest, in de kleinst mogelijke vorm.

In de praktijk van grote installaties bestaat hiervoor een alarm dat altijd afgaat — de waakhond — waarvan de afwezigheid het echte signaal is. Het Google-werkboek over betrouwbaarheid vat de houding samen in één zin: je bewakingssysteem is net zo belangrijk als elke andere dienst die je draait, en verdient dezelfde zorg.

Een geslaagde run is geen bewijs van gedaan werk

Dat "geslaagd" en "heeft zijn werk gedaan" uit elkaar kunnen lopen is geen anekdote. Een studie uit 2025 naar bouwtaken in de industrie noemt dit expliciet stille mislukking: taken die als geslaagd gemarkeerd staan maar hun werk niet of niet volledig afmaakten. De onderzoekers gebruikten het opnieuw draaien van gelukte taken als spoor, en vonden dat over 142.387 taken in 81 projecten 11 procent van de geslaagde taken opnieuw werd gedraaid, waarvan ruim een derde pas na meer dan een etmaal.

Let op wat dat getal wél en niet zegt. Het is een aanwijzing dat mensen structureel wantrouwen wat hun eigen groene vinkje beweert, en het gaat over bouwtaken in een ontwikkelstraat, niet over geplande taken op je eigen machine. Het is geen maat voor hoe lang een storing gemiddeld onopgemerkt blijft — die maat heb ik niet kunnen vinden in een vorm die ik durf over te nemen.


6 — Uitkomst

Je bewakingsplan

Wat er uit de scan hierboven volgt, als lijst. Verandert mee als je de vinkjes aanpast.


7 — Verantwoording

Wat hier wel en niet hard is

Hard: de matrix van welke laag welke storing vangt. Die volgt uit wat elke controle feitelijk leest — een controle die naar de wijzigingstijd van een logbestand kijkt, kan per definitie niets zeggen over de inhoud ervan. Ook hard: het gedrag van de shell in de derde werkbank, dat rechtstreeks uit de bash-documentatie komt.

Een model, geen meting: de detectietijden. Ze gaan uit van een bewaking die op vaste momenten kijkt en van drempels die je zelf instelt. In werkelijkheid komt daar variatie bij — de storing begint zelden precies vlak voor een controle. Lees de getallen als bovengrenzen binnen dit model, niet als voorspelling.

Bewust weggelaten: cijfers over hoe lang storingen in geplande taken gemiddeld onopgemerkt blijven. Die getallen circuleren wel — vaak toegeschreven aan onderzoek dat ze niet bevat — maar ik vond geen bron die de meetmethode publiceert. De zeventien dagen uit de inleiding zijn één waargenomen geval, geen statistiek.

Eén claim die ik onderweg heb moeten intrekken. Ik begon met de aanname dat het privacysysteem van macOS een geweigerde toegang omzet in een leeg resultaat zonder foutmelding — dat is de gangbare uitleg, en het zou het verhaal mooi rond maken. Nagemeten vanuit een echte achtergrondtaak klopt het niet: er komt wel degelijk een duidelijke weigering met een foutstatus. De stilte ontstaat een laag hoger, in de pijplijn die de status weggooit en de omleiding die de melding wegwerkt. Dat maakt het verhaal minder exotisch en nuttiger, want dat deel heb je zelf in de hand. De variant van de claim die over agenda- en contactgegevens gaat, heb ik niet kunnen toetsen en staat er daarom niet.

Niet behandeld: alles wat met schaal te maken heeft — verdeelde scheduling, taakwachtrijen met herprobeer-logica, en de vraag wanneer je van losse taken naar een echte orkestrator moet. Dit gaat over de tientallen taken die op één machine draaien en waarvan je zelf de enige beheerder bent.