← Wróć do bloga

Trzy testy zielone, jeden czerwony.
Scalone, bo `tail` powiedział, że jest dobrze

28.10.2026
Ten wpis to archiwum newslettera. Czytaj oryginał na Substack →

Ciao! 🇵🇱🇮🇹

Agent, który prowadzi moje pull requesty w projekcie celneCV, scalił zmianę mimo czerwonego testu. Poczekał na wynik, wypisał go i poszedł dalej, jakby wszystko było zielone.

Lecimy z tematem 🚀


Zielony ogon czerwonego testu

W celneCV mam prostą zasadę: pull request scalam dopiero wtedy, gdy wszystkie testy na GitHubie są zielone. Pilnuje tego agent w Claude Code, który wystawia moje zmiany i po zielonym CI sam je scala. 25 października scalił PR z samą dokumentacją, choć jeden z czterech testów był czerwony.

Objaw. W historii komend wszystko wyglądało zwyczajnie. Agent poczekał, aż testy się skończą, wypisał ich wynik i scalił. Nic się nie wywróciło, nie pojawił się żaden błąd. Dopiero w samym PR-ze zobaczyłem przy teście check (lint) słowo fail, a obok trzy razy pass.

Co się naprawdę działo. Komenda miała w uproszczeniu taki kształt:

gh pr checks 147 --watch | tail; gh pr merge 147

gh pr checks --watch czeka na koniec testów i kończy się kodem różnym od zera, gdy coś nie przeszło. Ten kod przepadł po drodze dwa razy. Pierwszy raz w potoku: bash ocenia cały łańcuch a | b po ostatnim poleceniu, a tail prawie zawsze kończy się sukcesem, bo ma tylko wypisać ostatnie linijki. Drugi raz na średniku, bo polecenie po ; rusza bez względu na wynik poprzedniego. Operator && w miejscu średnika też nic by nie dał. Pytałby tail, a tail odpowiadał „w porządku”.

Szkody nie było. Lint był czerwony już na main po nocnym pushu kolegi z projektu, a mój PR dotykał tylko dokumentacji. Zasada i tak została złamana.

Jak to wykryć u siebie. Przejrzyj skrypty i pliki CI pod kątem potoków kończących się na | tail, | head, | tee albo | grep, po których coś zależy od wyniku. W terminalu wystarczy jedna linijka: false | tail; echo $? wypisze 0. W bashu zaraz po potoku zmienna PIPESTATUS pokazuje kody wszystkich jego elementów. W GitHub Actions krok bez shell: bash działa z samym -e, bez pipefail, więc ta sama dziura może siedzieć w Twoim CI.

Jak naprawiłem. Rozdzieliłem sprawdzanie i scalanie na osobne kroki. Wynik testów trafia najpierw do pliku i jest czytany w całości. Scalenie przychodzi dopiero wtedy, gdy na liście są same pass. Drugi PR z tym samym czerwonym lintem zostawiłem otwarty, czeka na poprawkę. Tam, gdzie potok musi zostać, na początku skryptu powinno stać set -o pipefail.

Czego to uczy. Potok raportuje tylko o swoim ostatnim ogniwie. Jeśli od wyniku zależy decyzja, czytaj go z polecenia, które ten wynik wydało, a nie z tego, które go tylko wyświetliło.


🛠️ Narzędzie

set -o pipefail

To opcja basha, po której potok zwraca błąd, gdy zawiedzie którykolwiek jego element. Mam ją na początku skryptów do mojego serwera na Mikrusie (monitoring, diagnostyka, kopie zapasowe), bo wyniki idą tam przez kilka poleceń połączonych potokiem. Polecam, bo to jedna linijka, a bez niej false | tail udaje sukces. Najczęściej spotkasz ją w zestawie set -euo pipefail.

https://www.gnu.org/software/bash/manual/html_node/Pipelines.html


🌍 Ze świata


😄 Na koniec

Mem: kamera sterowana sztuczną inteligencją wzięła łysinę sędziego za piłkę i podążała za nią przez cały mecz

Kamera przez cały mecz była pewna, że śledzi piłkę. Mój łańcuch komend z tą samą pewnością patrzył na tail.


Jeśli ktoś w Twoim otoczeniu powinien to przeczytać, prześlij mu ten numer. To najlepsza forma wsparcia.

A jeśli wolisz wesprzeć bezpośrednio: postaw mi kawę.

Cieszę się, że tu jesteś! Aby nie przegapić kolejnych wpisów i pomóc mi tworzyć więcej takich treści, dołącz do grona subskrybentów. To nic nie kosztuje.

Dołącz do Marini Brief →