← CI/CD: build, validate, and deliver
08 / 8 · 60 MIN

Shell, results, and delivery evidence

Preserve failures while collecting diagnostics, distinguish variable scope, and check whether evidence matches the bytes proposed for delivery.

Observe a failure hidden by tee

The first Bash group runs a producer that prints assertion failed and exits seven, piped into tee to write report.txt. Without pipefail, the process returns zero; with pipefail, it returns seven. The report remains in both cases. Recorded execution uses Bash 3.2.57 on the Darwin host with explicit options; it does not represent a current GitHub runner’s version. GitHub documentation distinguishes shell invocation and applies pipefail when shell: bash is selected. During real diagnosis, inspect the effective command, including wrappers. Do not infer that testing passed from file existence. Transport evidence and result evidence answer different questions.

Retain diagnostics without erasing failure

The second group compares two short scripts. The first uses || true after failure and ends with a message, returning zero. The second saves exit code seven, writes a report, and exits with the saved code. The file remains available and failure stays observable. This local pattern teaches intent; in a real pipeline, also confirm how the platform executes later steps and publishes artifacts after failures. Adding an upload step is insufficient if it is never reached. Conversely, allowing diagnostic collection should not automatically authorize delivery. Define the condition for publishing evidence separately from the condition for promoting the candidate.

Distinguish the result from continuation policy

In GitHub Actions, outcome and conclusion distinguish a step’s original result from the effect of continue-on-error. When the step fails with that option, outcome can indicate failure while conclusion is success. This distinction comes from inspected documentation; it was not executed on a runner in this laboratory. Use it to review a critical control that considers only final conclusion. Continuing can help collect reports or evaluate other components, but policy must explicitly decide how to handle failure. In a fictional exercise, ask the team to identify who may accept an exception, what impact is known, and what evidence remains recorded. Do not rewrite history to obtain green.

Transfer process data through a contract

The third group exports DR_RELEASE_ID in one process and confirms demo-42. A second process started with a clean environment receives unset. The laboratory then writes release_id=demo-42 into a local file named by GITHUB_OUTPUT. Writing is verified, but no runner reads the file. Do not turn this observation into a claim of executed GitHub output handling. On the platform, the step contract, job publication, and consumption through the appropriate context must be respected. Also choose the right mechanism: a small identifier does not have the same lifecycle as a binary or report. Record origin, consumer, and required retention to avoid accidental dependence on one machine’s state.

Preserve literal data and artifact identity

The fourth group passes a benign title containing quotes, a semicolon, and an asterisk through an environment variable. Quoted printf returns the text intact; there is no eval or event-code interpolation. In the same group, release.txt changes from validated-release-42 to rebuilt-release-42 and its SHA-256 changes. The name remains but content equality fails. The experiment verifies no signature or producer provenance. Use these observations to separate representation and meaning: text should remain data, and a stable name should not replace candidate identity. Before promoting a rebuild, confirm evidence applicable to the new bytes and intended destination.

Rehearse the decision before the window

Reserve the workshop’s final part for the run 41 report associated with candidate A, copied into a delivery of B in run 42. Write the decision and what would change it. Successful upload proves transfer, not validation of B. Retrieving A can be proposed only if it remains appropriate for current state and destination and an accountable decision exists. Then review laboratory limits: local analysis of eight files, four Bash and temporary-file groups, without an external provider or deployment. Independent review and practice on an authorized platform remain necessary. Retain negative examples to detect regressions when updating the linter, without assuming every future version produces identical diagnostics.

check_status=0
# Original local demonstration: a deliberately failing check
(exit 7) || check_status=$?
printf "failed-check=7\n" > retained-report.txt
exit "$check_status"
# Observed: report remains; process exits 7
# This is not a complete production pipeline.
IN PRACTICE

A fictional job is green because tee succeeded although testing failed. The team needs to retain diagnostics and block promotion.

Common pitfalls

Using || true without retaining the result, assuming a global job environment, confusing an output file with runner processing, and artifact names with byte identity.

Related topics: Bash and Linux · Observability · Change Management

Take this idea with you

Diagnostic collection can continue after failure. The delivery decision must remain bound to the critical result and the candidate actually validated.

Create account

Reference: Workflow syntax for GitHub Actions · CI/CD practices 2026-09; scoped GitHub Actions GitLab Jenkins and Azure DevOps Services documentation