●DESIGN — Claude Design gets a major update: design-system imports, direct canvas editing, and more export formats●CODE — Claude Design can start from your local codebase and hand a design off to Claude Code to implement●FABLE — Fable 5, a Mythos-class model made safe for general use, is now available in Claude Code v2.1.170●FIX — Mid-stream connection drops now preserve partial responses instead of showing a raw error●SCROLL — A new wheelScrollAccelerationEnabled setting disables mouse-wheel scroll acceleration in fullscreen●TIER — The Claude Design beta is available to Pro, Max, Team, and Enterprise customers●DESIGN — Claude Design gets a major update: design-system imports, direct canvas editing, and more export formats●CODE — Claude Design can start from your local codebase and hand a design off to Claude Code to implement●FABLE — Fable 5, a Mythos-class model made safe for general use, is now available in Claude Code v2.1.170●FIX — Mid-stream connection drops now preserve partial responses instead of showing a raw error●SCROLL — A new wheelScrollAccelerationEnabled setting disables mouse-wheel scroll acceleration in fullscreen●TIER — The Claude Design beta is available to Pro, Max, Team, and Enterprise customers
An Article My Gate Rejected Got Published — The Cost of Chaining the Quality Gate and git push in One Call
In an unattended publishing pipeline, an article my quality gate had rejected went live anyway. The cause was chaining the gate and git push into a single shell call. Here is how the exit code gets swallowed, and a two-phase publish-marker design that refuses to push until every gate has demonstrably passed.
One morning I went cold reading back the logs. An article my quality gate had stopped the night before — flagged with 🛑 violation detected — was sitting live on the site. The gate log clearly said it had failed. And yet, in the same run, git push had fired and the article was published.
The gate itself wasn't the problem. The problem was that I had written verification and publishing as a single shell call. As an indie developer running unattended publishing across several sites, I've learned that how the shell interprets exit codes turns directly into incidents. So I want to record what was actually happening and how I rebuilt it.
What happened the moment I chained them
The task body looked roughly like this:
python3 article_gate.py "$JA" "$EN"; git push origin main
It reads top to bottom as "gate, then push," which looks safe. But ; ignores the success or failure of what came before it entirely. Even if the gate exits with code 1, the shell runs the next git push as if nothing happened. That one line always meant "push regardless of what the gate decided."
In unattended runs there's no moment where a human reads the log and stops it. The instant I joined them with ;, verification became decoration that protected nothing.
The difference between ; and &&, and the single surviving exit code
Swapping ; for && prevents most of this incident on its own.
# Dangerous: pushes even if the gate failspython3 article_gate.py "$JA" "$EN"; git push origin main# Better: pushes only if the gate succeededpython3 article_gate.py "$JA" "$EN" && git push origin main
&& runs the right side only when the left side exits 0. Most people know this much.
The trap is that an agent or scheduler treats this one line as a single command, and only the final exit code survives in its hands. With a && b, if a fails the whole thing is a failure — but you cannot tell apart "stopped at a" from "a passed and b (push) failed" without reading the log. When your execution layer only observes one success/failure value, the verification verdict never reaches the outside before the run barrels through to the irreversible step. That's exactly why I no longer consider && alone to be enough.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Why connecting a verification command and an irreversible action in one call swallows failures — the semicolon, pipes, command substitution, and set -e gaps — shown with reproducible code
✦A two-phase design using set -euo pipefail and a publish marker so push only runs after every gate has passed
✦Splitting verification and publishing into separate calls as a structural safeguard that forces evidence-before-action in unattended agent runs
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Pipes, command substitution, and set -e quietly let it through
Even with &&, verification is neutralized the moment the exit code mutates into a different value somewhere in the middle. Three cases are easy to hit in unattended operation.
1. Pipes. Pipe to tee for logging and $? becomes the exit code of the last command in the pipe — tee — which almost never fails. The gate can fail while the whole thing looks successful.
# Trap: $? belongs to tee. The gate's 1 disappearspython3 article_gate.py "$JA" "$EN" | tee gate.log && git push origin main
Set set -o pipefail and the whole pipe fails as soon as any stage inside it does.
2. Command substitution. Capture output into a variable and the exit code inside $() doesn't propagate outward.
# Trap: RESULT gets the output, but the gate's verdict is discardedRESULT=$(python3 article_gate.py "$JA" "$EN")git push origin main # nobody is judging what's in RESULT
3. Overtrusting set -e. Even with set -e, there are many contexts where it doesn't fire: when a command is on the left of && or ||, when it's the condition of an if, when it's inside a function or a pipe. "I added set -e, so it's safe" is the most dangerous assumption of all.
What I learned is that you cannot expect a verification exit code to "work automatically" — you have to observe it explicitly and branch on it yourself.
set -euo pipefailif ! python3 article_gate.py "$JA" "$EN"; then echo "🛑 article_gate violation. Exiting without push." exit 1fiif ! python3 templating_gate.py "$WORK" --check "$JA" "$EN"; then echo "🛑 templating_gate violation. Exiting without push." exit 1fi
if ! cmd; then ... fi is the least ambiguous way to observe an exit code with certainty. Only here does the gate become a gate instead of a decoration.
Turning publishing into "only when a marker exists"
Even once you read the exit code correctly, as long as verification and push share the same stretch of code, the risk remains that someone — possibly future me — collapses them back into one line. So I changed the design to physically separate the verification phase from the publish phase, with a "ready to publish" marker between them.
The verification phase creates a disposable marker carrying the article's slug only when all four gates pass.
# --- Phase 1: verify only. Never push here ---set -euo pipefailMARKER="$WORK/.ready-to-publish"rm -f "$MARKER"run_gate() { if ! "$@"; then echo "🛑 gate failed: $*" exit 1 fi}run_gate python3 article_gate.py "$JA" "$EN"run_gate python3 templating_gate.py "$WORK" --check "$JA" "$EN"run_gate python3 frontmatter_integrity.py "$WORK"run_gate python3 redirect_integrity.py "$WORK"# Only on full pass: write a marker stamped with slug and timeprintf '%s\n%s\n' "$SLUG" "$(date -u +%s)" > "$MARKER"echo "✅ All gates passed. Ready-to-publish marker created."
The publish phase runs in a separate call and refuses to push unless the marker exists, matches the target slug, and is recent enough.
# --- Phase 2: publish. Push only when the marker is valid ---set -euo pipefailMARKER="$WORK/.ready-to-publish"if [ ! -f "$MARKER" ]; then echo "🛑 No ready-to-publish marker. Assuming verification did not pass; aborting." exit 1fiMARK_SLUG=$(sed -n '1p' "$MARKER")MARK_TS=$(sed -n '2p' "$MARKER")AGE=$(( $(date -u +%s) - MARK_TS ))if [ "$MARK_SLUG" != "$SLUG" ]; then echo "🛑 Marker slug ($MARK_SLUG) does not match target ($SLUG). Aborting." exit 1fiif [ "$AGE" -gt 600 ]; then echo "🛑 Marker is too old (${AGE}s). Re-run verification." exit 1figit add content/git commit -m "Add: $SLUG (JA+EN)"git push origin mainrm -f "$MARKER" # disposable; prevents a stale marker mix-up next time
Carrying the slug and timestamp is the point. Checking the slug match prevents "publish a different article on the strength of last run's leftover marker," and checking freshness (ten minutes here) prevents "push now on the basis of a verification that passed long ago." The marker is always deleted after push and rebuilt from scratch each time.
In unattended runs, "splitting into separate calls" is itself the safeguard
The essence of the two-phase design isn't the single marker file. It's that splitting verification and publishing into separate runs forces an observation point to exist between them.
Collapse it into one call as gate && push and the gate's verdict never surfaces outside the execution layer before the irreversible step flows through. Split the calls and you can't reach phase 2 until you've received phase 1's exit code and output. If you delegate this to an agent, have it read phase 1's log, confirm ✅ All gates passed, and only then run phase 2 separately — that ordering of "evidence before the irreversible action" becomes guaranteed by design.
These days, when I review an unattended task body, the first thing I check is whether a verification command sits on the same call as something irreversible — a push, a deploy, a delete. If they're continuous, I split them even when they're joined by &&. It's a line I arrived at after a few scares running several sites by myself.
What to check first
Open one of your unattended tasks — a scheduled run, CI, or a deploy script — and look for any place where a verification command and an irreversible action share the same line or call. If they're joined by ;, you're one run away from an incident right now. Even with &&, check whether a pipe or command substitution is mutating the exit code, and where you can, split verification and publishing into separate calls with a marker between them. Starting with that one spot is enough.
I hope this helps anyone running several things unattended. I'm still learning as I operate, too.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.