---
title: "Bash — A && B || C Is Not If/Else"
aliases: [bash-short-circuit, bash-and-or-pitfall, deploy-script-bash-bug]
tags: [bash, shell, deploy, devops, debugging]
sources:
  - "daily/2026-04-24.md"
created: 2026-04-24
updated: 2026-04-24
---

# Bash — A && B || C Is Not If/Else

The bash pattern `A && B || C` looks like an if/else construct but is not. If `A` succeeds and `B` fails (exits non-zero), `C` still executes — even though `A` was true. In deploy scripts this silently sets a `FAILED` flag when the deployment actually succeeded, causing false failure reports or missed error handling.

## Key Points

- **`A && B || C` ≠ `if A; then B; else C; fi`** — if `B` exits non-zero, `C` runs even when `A` was true
- The correct substitute is an explicit `if/fi` block, not `&&/||` chaining
- Deploy scripts commonly use this pattern as: `[[ condition ]] && run_check || FAILED=1` — if `run_check` exits non-zero (e.g., grep found nothing), `FAILED=1` is set falsely
- The bug is invisible: the deploy log shows no error, the service works fine, but the script exits with failure status — CI marks the deploy as failed
- Most dangerous when `B` is a test command (`grep`, `curl`, `diff`) that legitimately returns 1 for "no match found"

## Details

### The Failure Mode

```bash
# WRONG — looks like: "if backend is running, check health, else mark failed"
[[ "$BACKEND_ONLY" == "true" ]] && check_backend_health || FAILED=1

# What actually happens:
# 1. [[ "$BACKEND_ONLY" == "true" ]] → exits 0 (true)
# 2. check_backend_health → exits 1 (curl found no match in log, but server is fine)
# 3. FAILED=1 executes — even though condition was true and we entered the "then" branch
```

The backend is healthy (HTTP 200 in the logs), but the script concludes deployment failed because `check_backend_health` returned non-zero for an unrelated reason (e.g., `grep` found no output lines).

### The Fix: Explicit if/fi

```bash
# CORRECT — unambiguous if/else
if [[ "$BACKEND_ONLY" == "true" ]]; then
    if ! check_backend_health; then
        FAILED=1
    fi
fi

# Or inline:
[[ "$BACKEND_ONLY" == "true" ]] && { check_backend_health || FAILED=1; }
```

The grouped `{ }` form keeps B and C together as a single unit — C only runs if B fails within that group.

### Why `&&/||` Chaining Is Tempting

```bash
# Reads naturally as "do A and then B, or else C"
mkdir -p /tmp/deploy && rsync -r . /tmp/deploy || echo "deploy failed"

# This works correctly ONLY IF rsync is the last command and we want:
# "if rsync fails → print error"
# NOT: "if mkdir fails → skip rsync AND print error"
```

`A && B || C` works correctly only when:
1. You only care whether the entire `A && B` chain succeeds (not the individual steps)
2. `B` never legitimately exits non-zero when `A` was true

In deploy scripts, `B` is almost always a health check that can legitimately return 1 — making `&&/||` unsafe.

### Diagnosing a False Failure

```bash
# Script exits 1 but the deployment worked?
# Add set -x to see what ran:
set -x
bash deploy.sh

# Or check exit codes manually:
check_backend_health; echo "health check exit: $?"
```

If `check_backend_health` exits 1 despite the service being healthy, it's returning 1 for "no match" or "no output" rather than actual failure.

### Real Incident (2026-04-24)

`deploy.sh` for NotebookLlama project had:
```bash
[[ condition ]] && run_check || FAILED=1
```

The backend was running (HTTP 200 in logs), but `run_check` returned 1 because `grep` found no matching log lines (empty output = exit 1 for grep). `FAILED=1` was set, script exited 1. User saw "deploy failed" when deployment was fine.

Fix: replaced all `&&/||` patterns in the deploy script with `if/fi` blocks (commit `8c5e01f`).

## Related Concepts

- [[wiki/concepts/monorepo-deploy-script-pitfall]] — another silent deploy script failure (subdirectory `.git` check)
- [[wiki/concepts/python-service-deployment-dotenv]] — Python service deploy checklist with explicit error handling
- [[wiki/architecture/troubleshooting-playbooks]] — symptom → root cause → fix for common failures

## Sources

- [[daily/2026-04-24.md]] — NotebookLlama deploy.sh flagged as failed when backend was healthy; `&&/||` pattern caused `FAILED=1` despite `[[ condition ]]` being true and service returning HTTP 200; fix: `if/fi` blocks (commit `8c5e01f`)
