Fehlerbehebung · GitHub Actions
GitHub-Actions-Cron-Schedule läuft nicht? Alle stillen Ursachen
Warum ein GitHub-Actions-Cron-Schedule nicht läuft oder auslöst — falscher Branch, 60-Tage-Auto-Deaktivierung, nur UTC, Lastverzögerungen, verworfene Läufe, Ausgabenlimits — und die Lösung für jede Ursache.
on: schedule ist der trügerischste Trigger in GitHub Actions. Er scheitert aus
Gründen, die nirgendwo einen Fehler erzeugen — kein rotes X, keine E-Mail,
kein Log. Diese Anleitung geht alle bekannten Ursachen durch, grob nach
Wahrscheinlichkeit sortiert.
Diagnose in 30 Sekunden:
| Symptom | Wahrscheinlichste Ursache | Abschnitt |
|---|---|---|
| Lief noch nie, kein einziges Mal | Datei nicht auf dem Default-Branch | §1 |
| Lief wochen-/monatelang, dann Stopp | 60-Tage-Deaktivierung (öffentliche Repos) | §2 |
| Läuft, aber 10–40 Minuten zu spät | Best-Effort-Warteschlange | §3 |
| Läuft zur „falschen“ Zeit | Cron ist immer UTC | §4 |
| Überspringt gelegentlich einen Lauf | Unter Last verworfen | §3 |
| Stopp nach einer Billing-Änderung | Ausgabenlimit (private Repos) | §5 |
1. Liegt die Workflow-Datei auf dem Default-Branch?
Geplante Workflows laufen nur vom Default-Branch (meist main). Ein Workflow
auf einem Feature-Branch — oder gemerged in irgendetwas anderes als den
Default-Branch — löst nie aus, egal wie korrekt der Cron-Ausdruck ist. Das heißt
auch: Ein Schedule, an dem Sie in einem PR arbeiten, läuft erst nach dem Merge.
# die Datei muss hier existieren:
git show origin/main:.github/workflows/nightly.yml
Zum Testen auf einem Branch ergänzen Sie neben dem Schedule einen
workflow_dispatch:-Trigger für manuelle Läufe:
on:
schedule:
- cron: "17 3 * * *"
workflow_dispatch: {} # manuelle Läufe während der Entwicklung
Eine weitere Feinheit: Es läuft der Schedule, der auf dem Default-Branch definiert ist. Den Cron-Ausdruck auf einem Feature-Branch zu ändern bewirkt bis zum Merge nichts.
2. Hat GitHub nach 60 Tagen Inaktivität deaktiviert?
In öffentlichen Repositories deaktiviert GitHub geplante Workflows automatisch, wenn das Repository 60 Tage keine Aktivität hatte. Das ist der Klassiker „lief monatelang, hörte dann still auf“ — und trifft überproportional genau die Repos, die auf Schedules angewiesen sind: Scraper, Backup-Jobs, Daten-Pipelines ohne regelmäßige Commits.
Prüfen Sie den Actions-Tab → Workflow auswählen. Wurde er deaktiviert, sehen Sie ein Banner: „This scheduled workflow is disabled because there hasn’t been activity in this repository for at least 60 days“ — mit einem Enable workflow-Button. GitHub verschickt vor der Deaktivierung auch eine E-Mail an den Autor, die aber leicht untergeht.
Reaktivieren behebt es bis zu den nächsten 60 ruhigen Tagen. Für ein von Natur aus ruhiges Repo gibt es drei dauerhafte Optionen:
- Echte Aktivität — jeder Commit setzt die Uhr zurück.
- Ein Keep-Alive-Step — manche Workflows committen eine Timestamp-Datei oder nutzen eine Keep-Alive-Action. Funktioniert, verschmutzt aber die Historie, nur um den Scheduler zufriedenzustellen.
- Den Trigger aus GitHub herausziehen — den Job als
workflow_dispatchbehalten und von einem externen Scheduler per GitHub-API auslösen lassen (siehe letzter Abschnitt). Externe API-Trigger zählen als Aktivität und sind zeitgenau.
3. Mit Verzögerungen rechnen — und mit verworfenen Läufen
GitHub führt geplante Workflows in einer Best-Effort-Warteschlange aus, und die offizielle Dokumentation sagt ausdrücklich, dass geplante Events bei hoher Last verzögert und verzögerte Läufe komplett verworfen werden können. Zwei praktische Konsequenzen:
- Verzögerungen von 5–30+ Minuten sind normal, am schlimmsten zur vollen
Stunde, wenn sich alle
0 * * * *-Jobs stapeln. Planen Sie auf eine ungerade Minute (17 * * * *,43 3 * * *), um dem Andrang auszuweichen — das nimmt zuverlässig die Spitze, beseitigt die Verzögerung aber nicht. - Unter hoher Last werden Läufe ohne Retry und ohne Benachrichtigung übersprungen. Wenn Ihr Job „alles seit dem letzten Lauf“ verarbeitet, schreiben Sie ihn tolerant gegenüber fehlenden Intervallen (das Fenster vom letzten erfolgreichen Lauf ableiten, nicht von „jetzt minus eine Stunde“).
Außerdem gilt die Untergrenze: Schedules laufen höchstens alle 5 Minuten, und selbst die landen in der Praxis unregelmäßig.
Ihre tatsächliche Verzögerung können Sie messen — created_at des Laufs mit der
geplanten Minute vergleichen:
gh run list --workflow nightly.yml --json createdAt,displayTitle --limit 10
Wenn Ihr Anwendungsfall minutengenaue Ausführung braucht (Abrechnungen, SLA-Reports, zeitkritische Benachrichtigungen), ist GitHubs Scheduler das falsche Werkzeug — der GitHub-Actions-Cron-Vergleich zeigt, was ein zeitgenauer Scheduler ändert.
4. Der Cron-Ausdruck ist UTC — immer
GitHub Actions hat keine Zeitzonen-Einstellung für Schedules. 0 9 * * * ist
09:00 UTC:
| Sie schreiben | UTC | Berlin (Winter) | Berlin (Sommer) |
|---|---|---|---|
0 9 * * * | 09:00 | 10:00 | 11:00 |
30 4 * * 1 | Mo 04:30 | Mo 05:30 | Mo 06:30 |
Ihr „9-Uhr-Job“ verschiebt sich lokal zweimal im Jahr um eine Stunde. Innerhalb von GitHub gibt es keinen Workaround, außer zwei Workflows zu pflegen und an den Zeitumstellungen umzuschalten — oder extern von einem zeitzonenbewussten Scheduler zu triggern.
Der Ausdruck selbst ist Standard-Cron mit 5 Feldern (kein Sekundenfeld, keine
@daily-Aliasse). Anführungszeichen beibehalten — ein nacktes * kann das
YAML-Parsing stolpern lassen:
on:
schedule:
- cron: "*/15 * * * *" # alle 15 Minuten (UTC)
- cron: "0 3 * * 1-5" # 03:00 UTC an Wochentagen — mehrere Einträge sind ok
Unsicher, was ein Ausdruck bedeutet? Der Cron-Ausdruck-Erklärer übersetzt ihn in Klartext.
5. Weitere stille Kill-Switches
- Manuell deaktiviert — jemand hat im Actions-Tab „Disable workflow“ geklickt. Die Datei existiert weiter, alles sieht normal aus, nichts läuft.
- Ausgabenlimit erreicht — bei privaten Repos stoppen alle Läufe, wenn das Actions-Minutenbudget erschöpft ist oder eine Zahlung fehlschlug.
- Forks — geplante Workflows sind in Forks standardmäßig deaktiviert.
- Repository archiviert — Archivieren stoppt alle Workflows.
- Organisations- oder Enterprise-Richtlinie — Admins können Actions deaktivieren, auf ausgewählte Repos beschränken oder benötigte Third-Party-Actions blockieren.
- Der vorherige Lauf läuft noch — kombiniert mit Concurrency-Regeln können „fehlende“ Läufe in Wahrheit übersprungene sein.
Der schnelle Weg, den echten Zustand per API zu prüfen:
gh api repos/OWNER/REPO/actions/workflows --jq '.workflows[] | {name, state, path}'
state zeigt disabled_inactivity, disabled_manually oder active — und
unterscheidet §2 von §5 sofort.
Das eigentliche Problem: Niemand sagt Ihnen, wenn der Schedule stoppt
Jede Fehlerursache oben ist still. GitHub benachrichtigt den Autor bei einem fehlgeschlagenen Lauf — aber ein Lauf, der nie startet, erzeugt nichts, worüber benachrichtigt werden könnte.
Das robuste Muster ist ein Dead-Man’s-Switch: Der Workflow pingt bei Erfolg eine Heartbeat-URL, und ein externer Monitor alarmiert, wenn der Ping ausbleibt — egal aus welchem Grund:
jobs:
nightly:
runs-on: ubuntu-latest
steps:
- run: ./run-the-actual-job.sh
- name: Erfolg melden
run: curl -fsS https://ping.steadycron.com/${{ secrets.STEADYCRON_PING_TOKEN }}
Jetzt erzeugen ein deaktivierter Workflow, ein verworfener Lauf, ein kaputtes Deployment und die 60-Tage-Deaktivierung dasselbe Ergebnis: einen ausgebliebenen Ping und einen Alarm in Slack oder per E-Mail innerhalb Ihrer Grace-Zeit. Details unter Heartbeat-Monitoring.
Und wenn das Timing selbst zählt (Abrechnungsläufe, Reports): Verlassen Sie
sich gar nicht erst auf GitHubs Best-Effort-Scheduler — behalten Sie den Job als
workflow_dispatch und triggern Sie ihn per GitHub-API von einem Scheduler mit
exaktem Timing und Retries:
# SteadyCron-HTTP-Job → GitHub-API → Ihr Workflow, pünktlich, mit Retries
method: POST
url: https://api.github.com/repos/OWNER/REPO/actions/workflows/nightly.yml/dispatches
headers:
Authorization: Bearer ${GITHUB_PAT}
Accept: application/vnd.github+json
body: '{"ref":"main"}'
Das löst drei Probleme auf einmal: exaktes Timing mit Retries, zeitzonenbewusste Zeitpläne, und — weil API-Trigger als Repository-Aktivität zählen — keine 60-Tage-Deaktivierung mehr.
Häufige Fragen
Warum läuft mein geplanter GitHub-Actions-Workflow überhaupt nicht?
Die zwei häufigsten Ursachen: Die Workflow-Datei liegt nicht auf dem Default-Branch (Schedules laufen nur vom Default-Branch), oder GitHub hat den Workflow in einem öffentlichen Repository nach 60 Tagen ohne Aktivität automatisch deaktiviert. Prüfen Sie den Actions-Tab auf ein Deaktivierungs-Banner und ob die Datei auf main existiert.
Welche Zeitzone verwendet GitHub-Actions-Cron?
Immer UTC. Es gibt keine Zeitzonen-Einstellung für on: schedule — 0 9 * * * bedeutet 09:00 UTC, also 10:00 oder 11:00 in Berlin, je nach Sommerzeit. Die lokale Laufzeit Ihres Jobs verschiebt sich zweimal im Jahr um eine Stunde.
Warum verzögert sich mein GitHub-Actions-Cron um 15–30 Minuten?
Geplante Workflows laufen in einer Best-Effort-Warteschlange. Verzögerungen von 5–30+ Minuten sind normal, am schlimmsten zur vollen Stunde, wenn sich alle 0 * * * *-Jobs stapeln. Eine ungerade Minute (z. B. 17 * * * *) reduziert die Verzögerung — unter hoher Last können Läufe aber komplett entfallen.
Wie oft kann ein GitHub-Actions-Schedule laufen?
Höchstens alle 5 Minuten — */5 * * * * ist die Untergrenze. In der Praxis machen Warteschlangen-Verzögerungen selbst 5-Minuten-Schedules unregelmäßig.
Benachrichtigt mich GitHub, wenn ein geplanter Lauf entfällt oder der Workflow deaktiviert wird?
Für einen verworfenen Lauf gibt es keinerlei Benachrichtigung — ein Lauf, der nie startet, erzeugt nichts, worüber benachrichtigt werden könnte. Bei der 60-Tage-Deaktivierung erscheint ein Banner im Actions-Tab (und eine E-Mail an den Autor), das leicht untergeht. Die zuverlässige Lösung ist ein externer Dead-Man's-Switch, der alarmiert, wenn der erwartete Lauf ausbleibt.