Một script kiểm tra thất bại nhưng bước CI vẫn thành công sau khi thêm bộ xử lý log ở cuối pipeline. Hãy kiểm tra exit status của cả pipeline trước khi nghi ngờ hệ thống CI bỏ qua lỗi.
Ví dụ trong bài đã chạy bằng Bash 3.2.57, đối chiếu tài liệu GNU Bash và GitHub Actions ngày 31/10/2026. Không có build, deploy hoặc workflow thật được kích hoạt. Các lệnh chỉ tạo lỗi giả bằng mã thoát, không đọc secret và không tác động repository.
1. Thử một lỗi có kiểm soát
Chạy từng lệnh sau từ terminal. Mỗi lần gọi mở một Bash riêng, tắt tùy chọn để phép đối chứng không phụ thuộc shell tương tác đang dùng. Không cần đổi shell mặc định của máy.
bash --noprofile --norc -c 'set +e; set +o pipefail; (exit 7) | cat; printf "pipeline=%s\n" "$?"'
bash --noprofile --norc -c 'set +e; set -o pipefail; (exit 7) | cat; printf "pipeline=%s\n" "$?"'
Hai dòng in lần lượt pipeline=0 và pipeline=7 trong phép thử. Command cuối là cat, đọc hết đầu vào và thoát thành công. Khi chưa bật pipefail, mã thoát của nó trở thành kết quả pipeline, che mất mã 7 của command đầu.
Lưu ý hai command Bash trên vẫn kết thúc bằng printf thành công. Chúng là phép quan sát mã lỗi, chưa phải mẫu script truyền lỗi ra CI. Đừng copy riêng phần in trạng thái rồi cho rằng shell con sẽ tự trả lại mã vừa in.
2. Bật pipefail rồi xử lý kết quả
Khi pipefail được bật, pipeline trả mã khác 0 ở vị trí ngoài cùng bên phải nếu có command thất bại. Nếu tất cả thành công, nó trả 0. Đây không phải số lượng command lỗi, cũng không nhất thiết là lỗi xuất hiện sớm nhất theo thời gian.
Để script truyền đúng mã ra bên ngoài và vẫn có chỗ ghi thông báo, có thể dùng điều kiện rõ ràng. Mẫu độc lập dưới đây cố tình kết thúc với mã 7; chạy nó trong terminal thử, không ghép vào pipeline production chưa được rà.
#!/usr/bin/env bash
set -o pipefail
if (exit 7) | cat; then
printf 'Kiem tra thanh cong\n'
else
pipeline_status=$?
printf 'Kiem tra that bai, ma=%s\n' "$pipeline_status" >&2
exit "$pipeline_status"
fi
Lưu thành file .sh và chạy bằng bash ten-file.sh. Tên file là file thử do bạn tạo. Trong command thật, thay phần tạo lỗi bằng bước kiểm tra của dự án, rồi xác minh cả nhánh thành công lẫn thất bại.
3. Pipefail khác với errexit
set -e ảnh hưởng việc shell tiếp tục hay dừng khi command trả lỗi, với các ngoại lệ theo ngữ cảnh như điều kiện if. Pipefail xác định kết quả pipeline. Bật một tùy chọn không đồng nghĩa đã hiểu đầy đủ hành vi của tùy chọn còn lại.
Mẫu trên dùng if để quản lý kết quả có chủ đích. Nếu muốn dùng set -e, đọc cả các trường hợp ngoại lệ của Bash và thử bằng lỗi giả. Một dòng cấu hình ở đầu file không thay thế việc quan sát mã thoát cuối của toàn bộ script.
Không thêm || true chỉ để làm CI xanh. Nó có thể có lý do khi một mã lỗi cụ thể là kết quả chấp nhận được, nhưng khi đó hãy ghi rõ quy tắc và phân biệt lỗi dự kiến với lỗi thực sự của công cụ.
4. Kiểm tra shell thật trên CI
Tài liệu GitHub Actions đang tham chiếu phân biệt shell không khai báo trên Linux/macOS với shell: bash khai báo rõ. Cách gọi Bash của hai trường hợp có tùy chọn khác nhau, trong đó trường hợp khai báo rõ có pipefail.
Fragment sau đặt shell cho một step trong job có sẵn. Nó không phải workflow hoàn chỉnh và không có trigger hoặc quyền triển khai.
steps:
- name: Observe pipeline failure
shell: bash
run: |
(exit 7) | cat
Step này được thiết kế để thất bại. Đọc lại YAML của dự án và shell runner thực tế thay vì suy từ terminal local. Trên máy chỉ có sh, không mặc định cú pháp hoặc option Bash đều dùng được.
Kiểm tra và lỗi thường gặp
Sau khi sửa, thử command đầu lỗi, command cuối lỗi và cả hai thành công. Đọc status ngay sau pipeline; một lệnh chạy xen giữa có thể thay $?. Nếu dùng consumer kết thúc sớm như head, producer có thể nhận SIGPIPE; cần xem đó có phải kết quả dự kiến của luồng dữ liệu hay không.
Phép thử trong bài xác minh việc truyền mã lỗi ở local, chưa chứng minh mọi runner dùng cùng cấu hình. Khi CI báo xanh sai, hãy ghi shell, tùy chọn và mã thoát cuối của script để tìm đúng lớp đang làm mất lỗi.


