Fail a CI Step When grep Finds a Pattern
A bare grep exits 0 when it finds a match, so it naturally fails a step when a forbidden pattern is present.
The most common grep job in CI is a gate: fail if something bad appears, or fail if something required is missing. Getting the exit-code logic right is the whole trick.
What it does
grep returns 0 on a match and 1 on no match, so you can build two opposite gates. To fail when a forbidden string appears, invert the logic: if grep finds it (exit 0), make the step exit non-zero. To fail when a required string is absent, let a bare grep fail naturally (exit 1).
Common usage
# FAIL if a forbidden pattern is found
if grep -rq "DO_NOT_COMMIT" src/; then
echo "Forbidden marker found"; exit 1
fi
# FAIL if a required pattern is missing
grep -q "BUILD SUCCESS" build.log
# one-liner: fail on any leftover debugger statement
! grep -rq "binding.pry" src/Options
| Idiom | Effect |
|---|---|
| grep -q X | Step fails if X is absent (exit 1) |
| ! grep -q X | Step fails if X is present |
| if grep -q X; then exit 1; fi | Explicit fail-on-match with a message |
| grep -q X || true | Never fail on this grep |
In CI
For "no forbidden strings" checks, ! grep -rq PATTERN . is the compact form; it exits 0 when nothing matches and non-zero when the pattern is found. Add --exclude-dir to avoid scanning vendored code. Print a helpful message in the if form so the failure log says what was found.
Common errors in CI
A fail-on-match gate that "never fails" is often using grep X || true, which swallows the result, or relying on grep -v whose exit code is inverted. A required-pattern gate that "always fails" may be running under set -e where an unrelated earlier grep already aborted. Keep each grep gate explicit and test both the present and absent cases.