Back openDesk Edu for a sovereign, open-source education — every vote counts.
Vote nowSave products you love by clicking the heart icon.
Der verwirrendste Zustand, in dem ein selbst gehosteter Runner sein kann,
ist dieser: Er meldet online, er ist busy=false, seine Labels passen
exakt zu deinem Workflow – und dein Warteschlangen-Job hängt trotzdem zwei
Tage fest. Genau damit habe ich einen Nachmittag verbracht. Der Workflow-Text
war korrekt. Der Runner war gesund. Der Job – wie sich herausstellte – hatte
nie wirklich existiert.
Das ist der Diagnosepfad, der das Deploy schließlich freigeschaltet hat, plus die einzeilige strukturelle Korrektur, mit der das nicht mehr stillschweigend passieren kann.
Als ich den hängenden Run über die CLI betrachtete, war der Widerspruch der erste echte Hinweis:
$ gh run view <run> --json status,jobs
queued | # der Run liest sich als "queued", aber...
jobs war eine leere Liste. GitHub hatte den Workflow-Run vermerkt, aber
nie einen Job materialisiert. Es gab nichts, das der Runner hätte
aufnehmen können – kein test-Schritt, keine Übermittlung, überhaupt kein
Arbeitsobjekt. Der Run las sich als queued, doch gh run cancel bestand
darauf, er sei schon abgeschlossen, und gh run rerun bestand darauf, er
laufe bereits. Er steckte in einer Grauzone fest, die weder Cancel noch
Re-run anfassen konnten.
Das ist die Signatur eines Geister-Warteschlangen-Eintrags: Die Warteschlangen-Metadaten sind kaputt – nicht deine Maschine. Einen Runner neu zu starten hilft hier nicht, weil der Runner nie das Problem war.
Bevor du GitHub die Schuld gibst, schließe den Runner schnell aus – denn er ist die häufigste Ursache, und oft ist er auch die echte:
Identität und Labels prüfen. Der Runner muss für das Repository
registriert sein und jedes Label tragen, das dein runs-on verlangt.
gh api repos/$ORG/$REPO/actions/runners \
--jq '.runners[] | "\(.name) \(.status) busy=\(.busy) labels=\([.labels[].name]|join(","))"'
Das Diagnoselog lesen, nicht die Oberfläche. Ein Runner, der in der
GitHub-Oberfläche „online" wirkt, kann trotzdem eine verklemmte
Job-Abfrageschleife haben. Entscheidend ist _diag/Runner_*.log:
Listening for Jobs
Successfully created session with migrated settings
JobDispatcher] Set runner/worker IPC timeout to 30 seconds
Steht dort Listening for Jobs und dann nichts mehr – keine Fehler – dann wartet der Runner wirklich auf Arbeit.
Die systemd-Unit trotzdem neu starten. Eine hängende Abfrageschleife ist die häufigste Ursache für „online, aber idle", und der Neustart ist billig und sicher:
sudo systemctl restart actions.runner.<org>-<repo>.<name>.service
Am Tag dieses Vorfalls war genau das unser anderes Problem – und der Neustart räumte einen Zombie-Job in Sekunden weg. Aber für den Geister-Warteschlangen-Run blieb der Runner idle, weil es keinen Job gab, den man ihm hätte überreichen können.
Der neu gestartete Runner war active, gesund, busy=false, lauschte – und
der Run stand trotzdem mit null Jobs auf queued. Dann ist die Diagnose
fertig und der Fehler liegt auf GitHub-Seite.
Das strukturelle Problem war, dass der Workflow nur durch einen Push auslösbar war:
on:
push:
branches: [main]
Ein Geister-Warteschlangen-Eintrag kann weder per Re-run
(gh run rerun → „already running") noch per Cancel
(gh run cancel → „already complete") angefasst werden und auch nicht neu
ausgelöst werden. Der einzige Weg zu einem frischen materialisierten Job
war ein neuer Push auf main – also ein synthetischer Commit nur, um die
Pipeline anzustoßen. Das ist eine untragbare Art, eine Deploy-Pipeline zu
betreiben.
Der strukturelle Fix ist ein Block und entfernt die gesamte Fehlerklasse – landet ein Run je in der Grauzone, kannst du einen frischen Run direkt erzwingen:
on:
push:
branches: [main]
workflow_dispatch: {} # manuelles Nachauslösen, wenn ein Run hängt
Das ist alles. workflow_dispatch in deinen Deploy-/Build-Workflow zu
nehmen, verwandelt eine Einbahnstraße in einen wiederherstellbaren Weg. Wenn
der nächste Run verklemmt, schiebst du keinen sinnlosen Commit, sondern
führst aus:
gh workflow run build-and-push.yml --ref main
In unserem Fall erzeugte genau dieser eine legitime workflow_dispatch-Zeile
auch den frischen, gesunden Run – den der (bereits neu gestartete) Runner
sofort aufnahm und direkt bis zu einem grünen Deploy durchzog, samt des
Features, das hinter der Geister-Warteschlange festhing.
_diag-Log etwas anderes
sagt. Ein gesunder, lauschender Runner mit passenden Labels verlagert
die Beweispflicht auf die GitHub-Warteschlange.jobs leer, aber der Status queued, hilft kein
Neustart.workflow_dispatch haben. Der Preis sind ein paar Bytes YAML; der
Nutzen ist, dass ein hängendes Deploy immer einen Befehl vom
Neustarten entfernt ist – statt eines erzwungenen Commits.Selbst gehostete Runner bekommen den Löwenanteil der Schuld an Pipeline-Staus. Meistens zu Recht. Aber wenn der Runner wirklich gesund ist und der Job nie existiert hat, ist die Lösung nicht mehr Pflege der Maschine – sondern ein manueller Reset-Knopf.