Source: [AI-written shell pipelines: keep failed runs from replacing reports — Skills for Business](<https://skillsforbusiness.ai/blog/ai-written-shell-pipelines-preserve-reports/>)

[← All articles](https://skillsforbusiness.ai/blog/)

Development · Guide

# AI-written shell pipelines: keep failed runs from replacing reports

A failed Bash pipeline can still overwrite a useful report. Test exit status and saved contents, then validate a candidate before replacing the previous file.

[Skills for Business](https://skillsforbusiness.ai/blog/editorial/)5 November 20266 min read

On this page

1.  [Why a red pipeline can leave a damaged report](https://skillsforbusiness.ai/blog/ai-written-shell-pipelines-preserve-reports/#why-a-red-pipeline-can-leave-a-damaged-report)
2.  [Compare the report before and after each run](https://skillsforbusiness.ai/blog/ai-written-shell-pipelines-preserve-reports/#compare-the-report-before-and-after-each-run)
3.  [Put validation before replacement](https://skillsforbusiness.ai/blog/ai-written-shell-pipelines-preserve-reports/#put-validation-before-replacement)
4.  [Review the final state, including the failure state](https://skillsforbusiness.ai/blog/ai-written-shell-pipelines-preserve-reports/#review-the-final-state-including-the-failure-state)
5.  [Keep the remaining operational limits visible](https://skillsforbusiness.ai/blog/ai-written-shell-pipelines-preserve-reports/#keep-the-remaining-operational-limits-visible)
6.  [Sources](https://skillsforbusiness.ai/blog/ai-written-shell-pipelines-preserve-reports/#sources)

What to take away

-   Bash pipeline status and the contents of an output file are separate acceptance checks.
-   Enabling pipefail can expose an upstream failure without preserving the previous report.
-   Generate and validate a candidate before replacing the report, and identify retained output as stale after failure.

When an AI assistant writes a shell command that refreshes a business report, check both the command’s exit status and the report left on disk. In Bash, enabling `pipefail` helps reveal a failed pipeline stage. It does not undo an output file that the command has already overwritten. Generate into a separate candidate, validate that candidate, and replace the report only after those checks succeed.

This matters for small operational scripts that combine exports, sort records or prepare a daily handoff. A file can exist, contain plausible rows and still represent an incomplete run. A reviewer accepting an AI-assisted change needs evidence about what downstream readers will receive, including after failure.

The worked example below uses invented ticket identifiers and an executed local shell fixture. It demonstrates a software behavior, not the reliability of a coding assistant. No named model, assistant product or customer workflow was tested.

## Why a red pipeline can leave a damaged report

The [Bash manual distributed by Debian](https://manpages.debian.org/trixie/bash/bash.1.en.html) describes two relevant behaviors. A pipeline normally takes its status from its last command; with `pipefail`, it reports the rightmost nonzero status when a stage fails. Separately, ordinary output redirection with `>` creates a file or truncates an existing one. Changing status handling does not change that file-opening behavior.

Consider this deliberately incomplete example in a disposable directory:

```
cat ./north.txt ./south.txt | LC_ALL=C sort > ./report.txt
```

Suppose `north.txt` contains `ticket-103`, while `south.txt` is missing. The first command can emit the available record and report an error for the missing file. The sorting command can successfully sort the input it received. A successful final stage does not establish that all required inputs arrived.

Adding `set -o pipefail` exposes the upstream failure through the pipeline’s status. However, the destination was still the report itself. The failure notification and the saved artifact now tell different parts of the story: the run failed, and its partial output replaced the previous contents.

Apple’s archived [Shell Scripting Primer](https://developer.apple.com/library/archive/documentation/OpenSource/Conceptual/ShellScripting/ShellScriptSecurity/ShellScriptSecurity.html), updated in March 2014, explains how pipeline status can mask an earlier failure and warns that automatic error handling does not replace explicit checks in complex scripts. This is established shell behavior, not a newly discovered AI defect.

## Compare the report before and after each run

For this article, an automated fixture executed five cases using GNU Bash 5.2.21 on Linux. Each case started in a separate temporary directory with a report containing the literal marker `LAST_GOOD_REPORT`. The fictional complete result was exactly two lines: `ticket-103` followed by `ticket-207`, each ending with a newline.

The fixture checked both the returned status and the report’s exact bytes. It also checked that no candidate files remained after the staged cases. The results were:

| Case | Exit status | Report after the run |
| --- | --- | --- |
| Direct output; second input missing | 0 | Only `ticket-103`; old contents lost |
| Direct output with `pipefail`; second input missing | 1 | Only `ticket-103`; old contents lost |
| Staged output with `pipefail`; second input missing | 1 | Original marker preserved |
| Staged output; both correct inputs present | 0 | Both expected ticket identifiers |
| Staged output; second input repeats the first identifier | 1 | Original marker preserved after validation rejects candidate |

The last case is a different failure. All pipeline commands can succeed while the result violates the reporting requirement. Error propagation detects unsuccessful commands; content validation checks whether successful commands produced an acceptable report. Neither replaces the other.

These are observations from a small deterministic fixture. They do not measure how often assistants generate a faulty pipeline, cover every Bash context or establish crash recovery. The fixture uses simple line-oriented text, not a general CSV parser or a production export format.

## Put validation before replacement

The following is the staged script used in the fixture. It assumes an owned, disposable working directory, fixed filenames, a regular-file report destination and a single writer. It uses Bash and GNU utilities, including GNU `mv -T`; it is not a portable script for every shell or operating system.

```
set -o pipefail
candidate=$(mktemp ./report.txt.XXXXXX) || exit 1
trap 'rm -f -- "$candidate"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM

if cat ./north.txt ./south.txt | LC_ALL=C sort > "$candidate"; then
  if ! cmp -s "$candidate" ./expected.txt; then
    printf '%s\n' 'Report validation failed' >&2
    exit 1
  fi
  mv -T -- "$candidate" ./report.txt || exit 1
else
  status=$?
  printf '%s\n' 'Report generation failed; previous report retained' >&2
  exit "$status"
fi
```

The candidate is created beside the destination. The pipeline writes there, leaving the report alone during generation. Only a successful pipeline reaches validation, and only a matching candidate reaches replacement. The exit trap removes an unused candidate on ordinary exit; the signal handlers request exit for interrupt and termination signals.

For this tiny fixture, `expected.txt` is an independently specified, literal two-line answer. A real report needs checks derived from its business definition: required input coverage, valid fields, permitted identifiers or reconciliation totals. Do not copy the two-line expectation into a changing production workflow, or let an assistant generate both the result and its supposed independent answer from the same mistaken assumption.

David Pashley’s [2013 practitioner guide](https://www.davidpashley.com/articles/writing-robust-shell-scripts/) discusses pipeline failures, cleanup traps and making changes in a separate copy before switching live data. Our example applies those established ideas to a narrower acceptance question: what exact report survives a failed refresh? It does not propose shell staging as a new technique.

## Review the final state, including the failure state

The January 2026 [Terminal-Bench paper](https://arxiv.org/pdf/2601.11868) offers a relevant evaluation principle. Section 2.1 describes testing properties of the final container state rather than judging the agent’s command text or console output. Section 2.3 discusses checking that task instructions and tests describe acceptable end states.

We apply that principle here as an editorial recommendation. The paper does not validate this script, and its benchmark results are not an estimate of today’s assistants on business reporting. Its useful connection is that a convincing command transcript is weaker evidence than checking the artifact the task was meant to produce.

For an AI-assisted patch, specify the failure expectation before asking for implementation. For example: “If either required input is unavailable, return failure, preserve the previously accepted report unchanged, and do not announce a fresh report.” Then run that case against the actual script under its actual launcher. Keep the status, relevant diagnostic and before-and-after content comparison with the review evidence.

The existing guide to [AI-generated tests and intended regressions](https://skillsforbusiness.ai/blog/ai-generated-tests-intended-regression/) addresses whether a test catches the required defect. Here, a test that asserts only a nonzero exit would accept the second row in our table despite the overwritten report. Add the preserved-content assertion because it represents a separate requirement.

## Keep the remaining operational limits visible

Preserving an old report can prevent partial replacement while still misleading a reader about freshness. After a failed refresh, the surrounding job should report failure and identify the retained artifact’s previous successful generation time. File existence alone must not become permission to label the report current.

The example also leaves production concerns unresolved. It does not coordinate concurrent writers, preserve an existing file’s permissions and ownership, validate hostile paths, guarantee storage durability after power loss or transact across several output files. A forced kill can bypass shell cleanup. A candidate in another filesystem also requires separate review of replacement behavior. Decide those requirements with the developer responsible for the environment before adapting the pattern.

If the helper is invoked through an agent skill, keep that failure contract in its instructions: a retained report is an older artifact, not a successful result from this attempt. Our [guide to script contracts in agent skills](https://skillsforbusiness.ai/blog/agent-skill-scripts-repeatable-rules/) covers the surrounding interface. Start acceptance review with one missing-input run and inspect both outcomes: the failure the caller receives and the report the next reader opens.

## Sources & further reading

Sources checked for this article. Research findings and vendor claims are distinguished in the text.

1.  [bash(1) manual](https://manpages.debian.org/trixie/bash/bash.1.en.html)GNU Bash authors; Debian Manpages · Accessed 5 November 2026
2.  [Shell Scripting Primer: Shell Script Security](https://developer.apple.com/library/archive/documentation/OpenSource/Conceptual/ShellScripting/ShellScriptSecurity/ShellScriptSecurity.html)Apple Developer Documentation Archive · Accessed 5 November 2026
3.  [Writing Robust Bash Shell Scripts](https://www.davidpashley.com/articles/writing-robust-shell-scripts/)David Pashley · Accessed 5 November 2026
4.  [Terminal-Bench: Benchmarking Agents on Hard, Realistic Tasks in Command Line Interfaces](https://arxiv.org/pdf/2601.11868)Merrill and colleagues; arXiv · Accessed 5 November 2026

Produced with AI-assisted research and writing. Examples and recommendations are our editorial synthesis unless attributed. This article does not claim independent product testing or human expert review. [Read our editorial policy.](https://skillsforbusiness.ai/blog/editorial/)

## Keep reading

[AI-written ZIP importers: validate the member you actually read](https://skillsforbusiness.ai/blog/ai-written-zip-importers-member-identity/)[AI-written SQL exclusions: decide what missing identifiers mean](https://skillsforbusiness.ai/blog/ai-written-sql-exclusions-missing-identifiers/)
