Jakie błędy w YAML naprawdę psują workflowy GitHub Actions
Co wiemy o źródłach problemów w YAML dla GitHub Actions
Składnia YAML w GitHub Actions jest jednocześnie prosta i bezlitosna. Ma kilka zasad, które są nieskomplikowane na papierze, ale w połączeniu z regułami samego GitHub Actions prowadzą do zaskakującej liczby błędów. Źródłem problemów są zwykle dwa poziomy:
- błędy czysto YAML – niepoprawne wcięcia, brak dwukropków, złe listy, tabulatory, złe łamanie linii;
- błędy specyficzne dla GitHub Actions – Workflow jest poprawnym YAML-em, ale nie spełnia schematu wymaganych pól i reguł GitHub Actions.
Błędy pierwszej kategorii zatrzymują wszystko już na etapie parsowania pliku workflow. GitHub w ogóle nie widzi struktury jako poprawnego workflow i zwraca komunikaty typu: YAML syntax error, invalid workflow file, Unexpected value. Druga kategoria jest trudniejsza, bo YAML przechodzi, ale GitHub Actions sygnalizuje błędy bardziej ogólne: job jest ignorowany, krok pomijany, albo workflow w ogóle nie startuje bez wyraźnego, oczywistego powodu.
Co wiemy? Najczęściej problemem nie jest sama „magia GitHuba”, tylko drobna literówka, nieprawidłowy poziom wcięcia lub pole umieszczone w złym miejscu. Czego często nie wiemy? W którym dokładnie miejscu YAML „łamie” schemat workflow lub gdzie struktura jest poprawna, ale niezgodna z logiką kolejności jobów, warunków if: czy zależności needs:.
Jak wyglądają typowe komunikaty błędów z GitHub
GitHub w zakładce Actions wyświetla kilka typów komunikatów powiązanych z walidacją YAML i strukturą workflow. Najczęstsze to:
- YAML syntax error – parser YAML nie był w stanie odczytać pliku; zwykle problem z wcięciem, dwukropkiem, myślnikiem lub tabulatorem;
- Unrecognized named-value lub Unrecognized function – błąd w ekspresji
${{ }}, np. literówka wgithub,env,secretsalbo użycie niedostępnej funkcji; - Unexpected value 'xyz’ – poprawny YAML, ale w danym miejscu schemat workflow nie przewiduje takiego pola, np.
step:zamiaststeps:alborun_on:zamiastruns-on:; - Invalid workflow file – ogólny komunikat, za którym zwykle kryje się brak wymaganych sekcji, np.
jobslubruns-onw jobie.
Część błędów pojawia się już na etapie validacji workflow – pipeline w ogóle się nie uruchamia. Inne wychodzą dopiero w trakcie działania jobów, gdy GitHub próbuje wykonać konkretny krok, odczytać zmienną, zbudować macierz albo rozwikłać zależności.
Błędy blokujące workflow kontra błędy runtime
Błędy w YAML w GitHub Actions można pogrupować według momentu, w którym się ujawniają:
- Błędy blokujące start workflow – GitHub nie widzi poprawnego workflow, nie tworzy nawet wpisu runs; w historii Actions nie ma nowej pozycji, albo jest czerwona ikona przy „workflow failed to run”. Typowe powody: nieprawidłowy YAML, brak sekcji
on:, brakjobs:, błędna definicja eventów. - Błędy walidacji na poziomie jobów – workflow startuje, ale konkretny job nie przechodzi walidacji schematu: brakuje
runs-on, niezgodne z formatemstrategy, błędne pola wsteps. - Błędy runtime – konfiguracja YAML i struktura workflow przechodzą, ale zawartość kroku jest błędna: komenda w
run:zwraca exit code != 0, akcja wuses:nie istnieje lub nie ma do niej dostępu, zmienna zsecretsjest używana poza dozwolonym kontekstem.
Najbardziej podstępne są błędy ciche: kroki pomijane przez warunki if: albo joby, które nigdy nie startują z powodu błędnego needs:. YAML jest poprawny, workflow w panelu wygląda na aktywny, ale ważny fragment procesu CI/CD nie wykonuje się w ogóle.
Praktyczne konsekwencje błędów YAML w Actions
Typowe skutki w praktyce to:
- pipeline nie startuje – commit został wypchnięty, ale nic się nie dzieje; w zakładce Actions brak nowego uruchomienia lub widać od razu czerwony komunikat o nieprawidłowym workflow;
- job się wywala na starcie – w logu jobu pierwszy lub drugi krok kończy się błędem, często związanym z niepoprawnym
uses:, złymi zmiennymi environment, brakiem tokenu czy nieprawidłowym checkoutem; - krok jest pomijany w ciszy – wszystko świeci się na zielono, ale kluczowy krok ma warunek
if:, który nigdy nie jest spełniony lub jest nadpisany na poziomie jobu; brak oczywistego „czerwonego” sygnału, że coś jest źle skonfigurowane.
Z punktu widzenia osoby odpowiedzialnej za DevOps i CI/CD kluczowe jest więc nie tylko prawidłowe pisanie YAML, ale też wypracowanie szybkiego sposobu diagnozowania: czy problem leży w samej składni YAML, w regułach GitHub Actions, czy w logice procesu, który workflow ma odzwierciedlać.

Podstawy składni YAML w kontekście GitHub Actions
Jak YAML jest interpretowany przez GitHub
Plik workflow (np. .github/workflows/ci.yml) przechodzi dwustopniowy proces:
- Parsing YAML – GitHub używa parsera YAML, który zamienia plik tekstowy na strukturę danych (mapy, listy, wartości skalarne). Na tym etapie wykrywane są klasyczne błędy składniowe: niezamknięte stringi, tabulatory, brakujący dwukropek.
- Walidacja struktury workflow – gotowa struktura YAML jest porównywana ze schematem GitHub Actions: sprawdzane są obowiązkowe sekcje (
on,jobs), dozwolone pola w jobach i krokach, typy wartości wstrategy,env,secretsitd.
Błędy z pierwszego kroku GitHub zgłasza jako typowe YAML syntax error. Błędy z drugiego kroku dostają zwykle formę unexpected value, invalid workflow lub bardziej opisowe komunikaty dotyczące konkretnych miejsc, np. „The workflow is not valid. .github/workflows/ci.yml (Line X, Col Y): Unexpected value 'step’”.
Zrozumienie, że GitHub nie „czyta” Twojego YAML bezpośrednio, tylko przechodzi przez te dwa etapy, pomaga rozdzielić debugowanie: najpierw upewnić się, że sam YAML jest poprawny, dopiero potem szukać niespójności ze schematem workflow.
Wcięcia, listy i mapy – fundamenty, które najczęściej zawodzą
YAML opiera się na trzech podstawowych konstrukcjach:
- mapy (słowniki) – pary klucz: wartość, np.
name: CI,runs-on: ubuntu-latest; - listy – elementy oznaczone myślnikiem, np. lista kroków w
steps:czy lista gałęzi wbranches:; - skalary – pojedyncze wartości: stringi, liczby, wartości logiczne (true/false).
Najważniejszym mechanizmem są wcięcia. Każdy poziom zagnieżdżenia jest tworzony przez większą liczbę spacji. Standardowo stosuje się 2 spacje na poziom:
name: CI
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
Klucz on jest na poziomie głównym. push jest zagnieżdżony o 2 spacje, branches również, a element listy - main ma dodatkowe 2 spacje (łącznie 4). Gdy wcięcia są niespójne, parser YAML zaczyna interpretować strukturę inaczej, niż oczekuje GitHub Actions – albo w ogóle nie jest w stanie jej zinterpretować.
Spacje kontra tabulatory i konsekwencje mieszania
YAML jest czuły na rodzaj znaków w wcięciach. Tabulatory są zabronione. Nawet jeśli edytor wizualnie pokaże ładne wcięcia, a plik „wygląda” poprawnie, jeden tabulator może wywołać poważny błąd:
jobs:
tbuild: # <-- tab tutaj
runs-on: ubuntu-latest
W takim przypadku workflow może w ogóle nie przejść walidacji z komunikatem: found character 't’ that cannot start any token albo bardziej ogólnym „YAML does not support tabs for indentation”. Aby tego uniknąć:
- ustaw w edytorze zamianę tabów na spacje,
- włącz widoczność znaków niewidzialnych, by szybko namierzać tabulatory,
- przy podejrzanych błędach składniowych przejedź po pliku narzędziem YAML linter, który wskaże linijki z tabami.
Cytowanie stringów i wpływ na ekspresje GitHub Actions
YAML pozwala używać stringów bez cudzysłowów, w pojedynczych cudzysłowach '...' lub w podwójnych "...". W kontekście GitHub Actions ma to znaczenie ze względu na interpretację ekspresji ${{ }} oraz znaków specjalnych.
Kluczowe różnice:
- bez cudzysłowów – YAML może spróbować zinterpretować wartość jako liczbę albo bool (np.
true,false,on), co czasem psuje logikę; lepiej unikać przy wartości typu cron lub ścieżki zawierające:; - pojedyncze cudzysłowy
'– string jest traktowany dosłownie, bez interpretacji sekwencji specjalnych;${{ }}w pojedynczych cudzysłowach nie zostanie potraktowane jako ekspresja do ewaluacji, tylko jako czysty tekst; - podwójne cudzysłowy
"– pozwalają na sekwencje specjalne (np.n), ale co ważniejsze:${{ }}wewnątrz nadal jest rozpoznawane jako ekspresja GitHub Actions.
Konkretny przykład:
env:
VERSION: '${{ github.sha }}'
Tutaj VERSION dostanie wartość dosłowną ${{ github.sha }}, bez podstawienia. Aby użyć ekspresji, powinno być:
env:
VERSION: "${{ github.sha }}"
To jedna z częstszych drobnych pułapek, które nie są błędem YAML, ale skutkują nieoczekiwanym zachowaniem workflowu.
Jak GitHub „widzi” workflow po sparsowaniu YAML
Po parsowaniu YAML GitHub Actions ma strukturę podobną do dużego obiektu JSON. Można ją sobie wyobrazić jak zagnieżdżony słownik:
{
"name": "CI",
"on": {
"push": {
"branches": ["main"]
}
},
"jobs": {
"build": {
"runs-on": "ubuntu-latest",
"steps": [
{
"name": "Checkout",
"uses": "actions/checkout@v4"
}
]
}
}
}
Na tym etapie YAML jest już historią. Zostaje czysta struktura, która musi być zgodna z oczekiwaniami GitHub Actions. To wyjaśnia, dlaczego niektóre błędy (np. literówki w nazwach kluczy) zgłaszane są jako „unexpected value”, a nie błędy YAML – bo parser uznał je za poprawny klucz w mapie, dopiero walidacja schematu workflow stwierdza, że takie pole nie ma sensu w danym miejscu.
Najczęstsze błędy składniowe YAML w plikach workflow
Problemy z indentacją w steps, jobs i strategy
Największym źródłem problemów w składni YAML w GitHub Actions są wcięcia przy definiowaniu jobs, steps i strategy. Małe przesunięcie potrafi zmienić wszystko.
Przykład błędnego wcięcia w steps:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build
run: npm run build
Dla oka wygląda to prawie dobrze, ale listek - name: Build ma za duże wcięcie. Parser YAML uzna, że jest to element listy zagnieżdżonej w poprzednim kroku, a nie nowy krok. GitHub Actions będzie miało problem albo zinterpretują to jako niepoprawną strukturę kroku. Poprawna wersja:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build
run: npm run build
Podobny problem pojawia się w strategy:. Przesunięcie o jeden poziom może sprawić, że runs-on znajdzie się wewnątrz strategy, zamiast na poziomie jobu:
Mylące dwukropki i łamanie linii w listach
Błędy z dwukropkami w YAML rzadko kończą się pięknym, jednoznacznym komunikatem. Często parser widzi poprawny YAML, ale w zupełnie innym kształcie, niż zakładał autor workflowu.
Typowy przypadek to mylenie mapy z listą:
steps:
- name: Run script
run:
echo "Hello"
echo "World"
Na pierwszy rzut oka wygląda to jak prosty krok. W praktyce wartość pod run: została potraktowana jako block scalar (wielolinijkowy string), ale bez jawnego operatora (| lub >). W zależności od narzędzia do lintowania można dostać ostrzeżenie albo ciche „przełknięcie” takiej konstrukcji.
Bezpieczniejszy wariant dla wielu linii komendy:
steps:
- name: Run script
run: |
echo "Hello"
echo "World"
Inny wariant problemu: wstawienie dwukropka do stringa bez cudzysłowów w miejscu, które YAML odczytał jako początek nowej mapy:
env:
DEPLOY_TARGET: production:blue
Niektóre parsersy poradzą sobie z tym bez problemu, inne zgłoszą błąd w stylu „found unexpected ’:’”. Bezpieczniej:
env:
DEPLOY_TARGET: "production:blue"
W przypadku GitHub Actions efekt jest prosty: jeśli YAML nie przechodzi parsowania, workflow w ogóle nie wystartuje, a w zakładce Actions przy pliku workflow pojawi się komunikat o błędzie składniowym.
Nieoczywiste problemy z multiline strings i heredocami
Wiele zespołów używa dłuższych skryptów w polach run:. Tu pojawia się konflikt między składnią shella a składnią YAML.
Przykład konstrukcji, która na pierwszy rzut oka wygląda sensownie, ale powoduje chaos w YAML:
steps:
- name: Deploy
run: >
cat <<EOF > config.json
{
"env": "prod",
"debug": false
}
EOF
Znaki <, >, {, } same w sobie nie są problemem. Kłopot pojawia się, gdy w środku takiego bloku trafia się linia zaczynająca się od - na tym samym poziomie wcięcia albo dwukropek bez cudzysłowów. YAML próbuje wtedy „wejść” w listę lub mapę, przerywając block scalar.
Bezpieczniejsze podejście w przypadku dłuższych wstawek JSON/konfiguracji to przeniesienie ich do osobnego pliku repozytorium lub użycie narzędzi typu jq zamiast wklejania surowego JSON-a jako tekst.
Błędne mieszanie aliasów i kotwic (&, *)
YAML wspiera kotwice (&) i aliasy (*), ale GitHub Actions nie interpretuje ich „magicznie” w kontekście ekspresji czy zmiennych. To wciąż tylko mechanizm na poziomie YAML.
Przykład prób optymalizacji, która bywa myląca:
defaults: &defaults
run:
shell: bash
working-directory: app
jobs:
build:
<<: *defaults
runs-on: ubuntu-latest
steps:
- name: Build
run: npm run build
Taka składnia jest poprawna w YAML 1.1/1.2, ale GitHub Actions nie wspiera operatora <<: (merge key). W rezultacie workflow jest niezgodny ze schematem i dostaje komunikat o nieoczekiwanej wartości. Parser YAML nie zgłosi błędu, dopiero walidacja workflow się posypie.
W praktyce stosowanie kotwic i aliasów w workflowach GitHub Actions prowadzi częściej do niejasnych błędów niż do realnych zysków. Prostszym i bardziej przewidywalnym rozwiązaniem są kompozytowe akcje lub reużywalne workflowy.

Błędy specyficzne dla GitHub Actions ukryte w YAML
„Unexpected value” – co faktycznie jest nie tak
Komunikat „Unexpected value 'X’” nie mówi o błędzie składni YAML, tylko o naruszeniu schematu GitHub Actions. Klucz wygląda jak poprawny element mapy, ale nie jest dozwolony w danym miejscu.
Klasyczne przypadki:
- literówka w nazwie pola, np.
step:zamiaststeps:,job:zamiastjobs:; - użycie pola z innego kontekstu (np.
runs-onwewnątrzsteps:); - nadpisanie zarezerwowanej struktury własnym kluczem.
Przykład, który generuje taki błąd:
jobs:
build:
runs-on: ubuntu-latest
step:
- name: Checkout
uses: actions/checkout@v4
YAML jest formalnie poprawny. Walidator workflow zgłosi jednak: Unexpected value 'step’, ponieważ w definicji jobu oczekuje steps, a nie step. Na tym etapie pytanie pomocnicze brzmi: „co wiemy?”. Wiadomo, że struktura jest poprawna, ale semantyka nie zgadza się z oczekiwaniami GitHub Actions.
Niewspierane pola i wersje schematu
GitHub Actions ewoluuje. Pojawiają się nowe opcje (np. if na jobach, concurrency, permissions), ale starsze dokumentacje, przykłady z blogów czy Stack Overflow nie zawsze są aktualne. W efekcie do workflow trafiają pola, które są poprawne w YAML, ale nieobsługiwane przez bieżącą wersję Actions.
Przykładowo, jeśli spróbować użyć timeout-minutes wewnątrz pojedynczego kroku (zamiast na poziomie jobu):
steps:
- name: Long task
timeout-minutes: 10
run: ./very-long-task.sh
Walidator odpowie błędem o nieoczekiwanej wartości timeout-minutes przy kroku. Pole jest wspierane, ale wyłącznie na poziomie jobu. Odczyt dokumentacji lub skorzystanie z podpowiedzi schematu (np. w VS Code z włączonym schema validation dla GitHub Actions) pozwala szybko zawęzić przyczynę.
Nieprawidłowe typy wartości w strategy i matrix
strategy: i matrix: są szczególnie wrażliwe na typy danych. YAML chętnie „zgaduje” typ (np. liczba, bool), a GitHub Actions oczekuje konkretnych struktur.
Typowy błąd: zdefiniowanie wartości matrix jako pojedynczego stringa zamiast listy:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: 18
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
YAML zinterpretuje 18 jako liczbę. GitHub Actions oczekuje listy wartości dla matrix (nawet jeśli zawiera pojedynczy element). Skutkiem będzie komunikat w stylu: The matrix node-version must be an array lub ogólniejszy błąd walidacji.
Poprawny wariant:
strategy:
matrix:
node-version: [18]
lub klasycznie:
strategy:
matrix:
node-version:
- 18
Analogiczny problem pojawia się przy polu fail-fast (bool) czy max-parallel (liczba). Jeśli wartość zostanie zacytowana niefortunnie lub pochodzi z ekspresji, walidacja może zgłosić problem z typem, choć YAML sam w sobie nie widzi błędu.
Ekspresje ${{ }} w miejscach, gdzie GitHub ich nie ewaluje
Ekspresje GitHub Actions nie działają wszędzie tak samo. Klucz, w którym są używane, może być ewaluowany albo traktowany jako zwykły string. To źródło wielu błędnych założeń.
Przykład, który wygląda logicznie, ale nie zadziała zgodnie z intencją:
jobs:
${{ github.ref_name }}:
runs-on: ubuntu-latest
steps:
- run: echo "Hello"
Nazwa joba (klucz w mapie jobs:) nie wspiera ekspresji. W efekcie parser YAML potraktuje ${{ github.ref_name }} jako literalny string, a workflow będzie miał job o tej właśnie nazwie, bez podstawienia. Nie jest to błąd syntaktyczny ani walidacyjny – to błąd założeń.
Inny przykład dotyczy env: na poziomie workflowu, gdzie ekspresje nie zawsze są ewaluowane w momencie, którego oczekuje autor. Część wartości jest rozwiązywana dopiero w runtime. Gdy diagnoza się przedłuża, pomaga proste pytanie: „co wiemy?” – wiadomo, że YAML jest poprawny, ale czy miejsce użycia ekspresji jest wspierane przez Actions?
Zachowanie domyślne vs. nadpisania w jobs i steps
Konflikty między wartościami domyślnymi a nadpisaniami często wyglądają z poziomu YAML na nieszkodliwe, a jednak psują logikę workflowu. Dotyczy to zwłaszcza pól env, defaults i permissions.
Przykład potencjalnie mylącej konfiguracji:
env:
NODE_ENV: production
jobs:
test:
runs-on: ubuntu-latest
env:
NODE_ENV: ${{ github.event.inputs.env }}
steps:
- name: Print env
run: echo $NODE_ENV
Na poziomie YAML jest wszystko w porządku, ale jeśli github.event.inputs.env okaże się pusty (np. manualne uruchomienie bez podania inputu), job dostanie NODE_ENV jako pusty string. Domyślna wartość production zniknie, bo bliższe zasięgi w env nadpisują dalsze.
W praktyce do diagnozy pomaga wprowadzenie tymczasowego kroku diagnostycznego z wypisaniem wszystkich zmiennych środowiskowych lub kluczowych pól JSON eventu.
Dlaczego workflow nie startuje? Błędy w sekcji on: i eventach
Nieprawidłowe połączenie eventów push/pull_request
Sekcja on: jest pierwszym miejscem, które decyduje, czy workflow w ogóle wystartuje. Błąd w tej części rzadko daje „czerwony” krzyżyk – częściej kończy się ciszą.
Przykład konfiguracji, która wygląda poprawnie, ale nie robi tego, czego oczekuje autor:
on:
push:
branches:
- main
pull_request:
branches:
- main
paths-ignore:
- "docs/**"
Intencja: reagować na każde push do main oraz na każdy pull_request do main, z wyjątkiem zmian w docs. Rzeczywistość: paths-ignore dotyczy tylko eventu pull_request, push nadal będzie uruchamiało workflow dla zmian w dokumentacji.
To nie jest błąd YAML, ani błąd walidacji – to różnica między oczekiwaniami a definicją. W logach nie ma jednoznacznej wskazówki, trzeba spojrzeć na definicję eventów i ich pola.
Nieczytelne połączenia warunków w on:
Zagnieżdżenia w on: potrafią zrobić z prostego filtra dość złożoną strukturę. Drobne przesunięcie wcięcia zmienia logikę.
on:
push:
branches:
- main
- "release/*"
paths:
- "src/**"
pull_request:
paths:
- "src/**"
- "tests/**"
Konfiguracja jest poprawna i czytelna. Problem pojawia się, gdy przez przypadek wcięcie zostanie przesunięte tak:
on:
push:
branches:
- main
- "release/*"
pull_request:
paths:
- "src/**"
- "tests/**"
- "docs/**"
W oczach YAML paths: przestaje dotyczyć push. Zostaje tylko przy pull_request. Efekt: workflow odpala się na push do wymienionych gałęzi niezależnie od ścieżek, co odbiega od pierwotnej intencji. YAML nie zgłasza błędu, GitHub Actions też – zachowanie po prostu się zmienia.
Eventy wymagające dodatkowych pól (workflow_dispatch, schedule)
Część eventów ma własne zasady. workflow_dispatch działa bez dodatkowej konfiguracji, ale schedule wymaga pola cron. Błędny cron nie zawsze jest czytelnie sygnalizowany.
Przykład pozornie poprawnego harmonogramu:
on:
schedule:
- cron: "0 0 * * 7"
Dla części administratorów „7” kojarzy się z niedzielą, dla innych z sobotą (w zależności od narzędzi). GitHub używa standardu CRON, gdzie 0–6 to niedziela–sobota, a 7 jest alternatywnie niedzielą. To nie jest błąd techniczny, ale źródło nieporozumień, gdy ktoś porównuje zachowanie z zewnętrznym narzędziem CRON, które interpretuje pole inaczej.
Znacznie bardziej problematyczny jest rzeczywisty błąd składni CRON:
on:
schedule:
- cron: "0 0 * *"
Brak piątego pola. GitHub zgłosi błąd walidacji workflowu, ale komunikat będzie odnosił się do sekcji schedule, a nie do YAML jako takiego. Narzędzie zewnętrzne do walidacji CRON pomaga tu oszczędzić czas.
Filtrowanie po ścieżkach – pułapki z paths i paths-ignore
Ścieżki, które nigdy nie pasują (i te, które pasują za szeroko)
Filtry paths i paths-ignore akceptują globy w stylu minimatch. Rozjazd między intuicją a faktycznym działaniem jest częstym źródłem „znikających” workflowów.
Typowy przykład niepasującego globu:
on:
push:
paths:
- "src/*"
Intencja: reagować na każdą zmianę w src. Rzeczywistość: src/* obejmuje tylko pliki bezpośrednio w katalogu src, ale już nie w src/components/Button.tsx. Większość repozytoriów ma zagnieżdżoną strukturę, więc workflow praktycznie nigdy nie startuje.
Wariant, który faktycznie pokrywa przypadek zagnieżdżony:
on:
push:
paths:
- "src/**"
Druga strona medalu to filtry zbyt szerokie:
on:
push:
paths-ignore:
- "docs/*"
Edytowany jest plik docs/guide/intro.md, workflow i tak się uruchamia, bo docs/* nie obejmuje głębszych poziomów. Potrzebny jest wzorzec docs/**. YAML nie zgłasza błędu, GitHub Actions też – dopiero porównanie listy zmienionych plików z definicją filtra ujawnia, co się dzieje.
Pomaga prosta kontrola: „co wiemy?” – wiemy, jakie pliki zmieniono (zakładka Files changed w PR), można więc mechanicznie sprawdzić, czy którakolwiek ścieżka pasuje do globów z sekcji on:.
Filtry po typach eventów w pull_request i pull_request_target
Eventy pull_request i pull_request_target mają dodatkowe filtry types. Źle dobrane wartości skutkują tym, że workflow startuje tylko w jednym z cykli życia PR, choć autor zakładał, że zadziała „zawsze”.
on:
pull_request:
types: [opened]
branches:
- main
Taka konfiguracja uruchomi workflow wyłącznie przy tworzeniu PR. Aktualizacje gałęzi, force push czy zmiana opisu PR nie wywołają nowego runa. To nie błąd YAML, ale rozjazd między definicją a oczekiwaniami.
Częsty błąd polega też na wymieszaniu pull_request i pull_request_target bez zrozumienia różnicy kontekstów uprawnień. Zdarza się, że workflow na pull_request_target nie uruchamia się wcale dla forków, gdy repozytorium ma zaostrzone polityki bezpieczeństwa – YAML jest poprawny, lecz zdarzenie jest blokowane konfiguracyjnie przez organizację.
Eventy ograniczone do konkretnych gałęzi i tagów
Filtrowanie push i create/delete po gałęziach i tagach to kolejny obszar pozornie prosty, który generuje „ciche” błędy. Problemem jest mieszanie branches i tags oraz niejasne wzorce.
on:
push:
branches:
- "v*"
Założenie: workflow wystartuje przy wypchnięciu taga v1.0.0. Fakty: filtr branches dotyczy wyłącznie gałęzi, a nie tagów. Push taga nie spełnia warunku i workflow milczy. Potrzebne jest osobne pole:
on:
push:
branches:
- main
tags:
- "v*"
Odwrotna sytuacja: zdefiniowane są tylko tags, a zespół oczekuje reakcji na merge do main. Diagnostyka: porównanie typu refa (refs/heads/... vs refs/tags/...) z sekcją on:.

Pułapki w definicji jobów, matrix i dependencies (needs)
Joby bez nazw vs. identyfikatory jobów w needs
GitHub Actions rozróżnia id joba (klucz w mapie jobs:) oraz przyjazną nazwę (name: wyświetlaną w UI). needs: odwołuje się do identyfikatorów, nie do nazw. To proste założenie, które często jest mylone.
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- run: echo "build"
test:
name: Test
needs:
- Build
runs-on: ubuntu-latest
steps:
- run: echo "test"
Workflow wygląda sensownie, ale needs: - Build jest błędne – GitHub nie znajdzie joba o id Build, bo istnieje build>. Skutkiem będzie błąd walidacji, często zgłaszany dopiero po zaczytaniu workflowu.</code>
Poprawny wariant:
jobs:
build:
name: Build
runs-on: ubuntu-latest
test:
name: Test
needs:
- build
runs-on: ubuntu-latest
Prosty test diagnostyczny: „co wiemy?” – znamy faktyczne klucze pod jobs:. To one są jedynym poprawnym odniesieniem dla needs:, niezależnie od name:.
Job zależny od jobu, który nigdy się nie uruchamia
needs: opisuje zależności, ale nie gwarantuje, że job, od którego zależamy, faktycznie wystartuje. Połączenie warunku if: na wcześniejszym jobie z needs na kolejnym bywa źródłem zaskoczeń.
jobs:
build:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- run: echo "build"
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- run: echo "deploy"
Jeśli workflow odpala się także na innych gałęziach niż main, job build będzie tam omijany ze statusem skipped. deploy w takiej sytuacji również zostanie pominięty, ponieważ jego wymagany dependency nie zakończył się statusem sukcesu. YAML nie sygnalizuje problemu, błędu walidacji nie ma – zależności po prostu redukują część workflowu do „skipniętego” drzewa.
W logach warto wtedy sprawdzić nie tyle sam job, co statusy w drzewie needs, widocznym w szczegółach runa.
Matrix, który wygląda poprawnie, ale nie generuje kombinacji
Definicja strategy.matrix jest wrażliwa nie tylko na typy, lecz także na strukturę. Błędne zagnieżdżenie może sprawić, że matrix składa się z jednego wymiaru lub jest ignorowany.
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
include:
node-version: [18, 20]
steps:
- run: echo "Node ${{ matrix.node-version }}"
Autor oczekuje dwóch runów (18 i 20). Faktycznie include: zdefiniowane w ten sposób jest pojedynczym wpisem mapy, nie listą. GitHub Actions wymaga listy obiektów pod include. Efekt: workflow może zwrócić błąd walidacji albo zignorować matrix, w zależności od kombinacji pól.
Poprawnie:
strategy:
matrix:
node-version: [18, 20]
lub, jeśli include faktycznie jest potrzebne:
strategy:
matrix:
node-version: [18, 20]
include:
- node-version: 18
extra-flag: "--legacy"
Druga kategoria problemów to matrix z pustą listą:
strategy:
matrix:
node-version: []
Formalnie poprawne dla YAML, w praktyce skutkuje brakiem jakichkolwiek jobów z tego matrixa. Czasem to skutek wcześniejszego użycia fromJSON lub ekspresji budującej listę dynamicznie. Diagnostyka: wypisanie wygenerowanej wartości matrixa (np. przez tymczasowy krok run: echo '${{ toJSON(matrix) }}').
Konflikty fail-fast i max-parallel z oczekiwaniami
Polom fail-fast i max-parallel rzadko poświęca się uwagę przy diagnozie błędów, tymczasem potrafią mocno zmienić zachowanie matrixa.
strategy:
fail-fast: true
max-parallel: 1
matrix:
node-version: [16, 18, 20]
Założenie: testy dla wszystkich wersji Node uruchomią się po kolei i pokażą pełen obraz. Rzeczywistość: przy pierwszej porażce pozostałe kombinacje zostaną anulowane. W logach widać, że część runów matrixa otrzymuje status cancelled, co można pomylić z problemem infrastrukturalnym.
Z kolei użycie stringa zamiast liczby:
strategy:
max-parallel: "2"
może spowodować błąd walidacji typu albo – przy niektórych narzędziach do autouzupełniania – niejednoznaczny komunikat. Sam YAML widzi tu zwykły string, problemem jest dopiero interpretacja przez Actions.
Niewidoczne zależności przez needs w matrixie
Łączenie matrixa z needs bywa źródłem bardziej subtelnych błędów, zwłaszcza gdy joby zależne nie mają matrixa lub używają innej osi.
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
steps:
- run: echo "build ${{ matrix.node-version }}"
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- run: echo "deploy"
Job deploy uruchomi się tylko raz, po wykonaniu wszystkich kombinacji build. Dla części zespołów to zaskoczenie, bo oczekują, że deploy odpali się po każdej kombinacji osobno. YAML nie daje tu żadnego ostrzeżenia – to kwestia modelu wykonania Actions.
Jeśli pożądane jest powiązanie 1:1, deploy także musi być jobem z matrixem, a needs musi wskazywać job z tym samym zakresem matrixa, np. przy użyciu tej samej osi:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20]
steps:
- run: echo "build ${{ matrix.node-version }}"
deploy:
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
node-version: [18, 20]
steps:
- run: echo "deploy ${{ matrix.node-version }}"
Jeśli logika jest bardziej złożona, konieczne bywa przejście na artefakty lub manualne sterowanie, zamiast polegania na automatycznym powiązaniu matrixów.
Warunki if: na jobach a statusy w needs
Kolejna grupa problemów pojawia się przy łączeniu warunków if: z needs. Warto rozdzielić dwa fakty: warunek if: na jobie decyduje, czy job w ogóle wejdzie do wykonania, natomiast pola typu if: success() lub if: failure() na krokach decydują o uruchomieniu kroku. Pomieszanie tych dwóch poziomów prowadzi do workflowów, które „kończą się zielono”, ale realnie nie zrobiły połowy pracy.
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: exit 1
notify:
runs-on: ubuntu-latest
needs: test
if: failure()
steps:
- run: echo "Tests failed"
Intencja: wysłać powiadomienie, jeśli testy padły. W praktyce if: failure() na jobie notify dotyczy całego workflowu w tym momencie, czyli jest ewaluowane po statusie needs. Jeżeli test faktycznie zakończy się błędem, notify będzie miał warunek spełniony i uruchomi się – to akurat działa. Problemy zaczynają się, gdy warunek obejmuje więcej kontekstu lub błędnie uwzględnia inne joby, które są jeszcze w toku albo zostały pominięte.
Bezpieczniejszym podejściem bywa jawne odwołanie do statusu konkretnego joba w needs:
if: needs.test.result == 'failure'
Taki zapis nie pozostawia pola do interpretacji – bazuje na statusie konkretnej zależności, a nie na globalnym stanie workflowu.
Ukryte błędy przez mieszanie defaults, env i lokalnych nadpisań
Joby i kroki mogą dziedziczyć ustawienia z poziomu workflowu. Konflikty powstają, gdy globalne defaults i env są nadpisywane lokalnie w sposób, który łamie oczekiwania co do typu lub kształtu danych.
defaults:
run:
shell: bash
working-directory: backend
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: npm test
- run: npm run build
working-directory: frontend
Autor chce, by drugi krok wykonał się w frontend. YAML jest poprawny, ale working-directory ustawione na poziomie kroku nie ma wpływu na domyślne run.working-directory z defaults. W efekcie oba polecenia wykonują się w backend. Tu błąd nie leży w samej składni, lecz w założeniu o tym, jak działa nadpisywanie wartości.
Najważniejsze punkty
- Źródła problemów dzielą się na dwie główne kategorie: czyste błędy YAML (wcięcia, dwukropki, listy, tabulatory) oraz błędy specyficzne dla GitHub Actions (poprawny YAML, ale niezgodny ze schematem workflow).
- GitHub komunikuje różne typy usterek: od klasycznego „YAML syntax error” przy problemach ze składnią, po „Unexpected value” i „Invalid workflow file”, gdy struktura nie spełnia wymagań Actions.
- Błędy można uporządkować według momentu ujawnienia: blokujące start workflow (workflow w ogóle nie rusza), błędy walidacji na poziomie jobów (job jest ignorowany) oraz błędy runtime (kroki wywalają się podczas wykonywania).
- Najbardziej podstępne są błędy „ciche”: joby i kroki pomijane przez źle ustawione if: lub needs:, przez co wszystko świeci się na zielono, ale kluczowa część procesu CI/CD nigdy się nie wykonuje.
- Typowe objawy w praktyce to: pipeline, który nie startuje po pushu, job kończący się błędem już w pierwszych krokach (np. z powodu złego uses: lub env) oraz kroki, które są stale pomijane bez wyraźnego sygnału błędu.
- GitHub przetwarza workflow dwustopniowo: najpierw parsuje YAML, potem sprawdza zgodność ze schematem Actions; skuteczne debugowanie wymaga rozdzielenia tych dwóch etapów i świadomego szukania, na którym z nich nastąpiło załamanie.
- Kluczowa kompetencja dla osób od CI/CD to nie tylko poprawne pisanie YAML, ale też szybkie ustalenie, czy problem leży w składni, w regułach GitHub Actions, czy w samej logice procesu (zależności, warunki, kolejność jobów).






