We shipped a DeFi platform last year. Three senior engineers, six months, clean architecture. And yet we had a bug that took us two days to find.
The culprit? A Bash deployment script that silently ate an error, continued running, and handed our users a half-initialized state. Nobody wrote bad code. The tool just let it happen.
That moment changed how we think about engineering at Gerus-lab.
The Uncomfortable Truth
When a bug ships, we ask who wrote this. But the better question is what made this possible.
Tools, frameworks, and languages are not neutral. They have opinions baked in. Some opinions protect you. Others actively set traps.
After 14+ products shipped — from GameFi platforms on Solana to SaaS backends to AI automation systems — we've learned to blame the stack first and the developer second.
Here is what we mean.
Trap #1: Silent Failures by Default
The Bash incident I described above is not a one-off. By default, Bash continues executing even when a command fails. It does not throw. It does not alert. It just... keeps going.
#!/bin/bash
# This script will happily continue if anything fails
build_app
deploy_to_production # runs even if build failed
notify_slack "Deploy successful!"
Every serious Bash script needs this at the top:
set -euo pipefail
But you have to know this. It is not the default. The default is chaos.
We burned two engineering days on this in production. After that, we added set -euo pipefail to our script templates and made it a code review checklist item. That is an institutional fix for a language design problem.
If you are building on old tooling without these guardrails, you are not managing your team. You are managing landmines.
Trap #2: Footguns Disguised as Features
JavaScript has this delightful behavior:
const user = { name: "Alex", role: "admin" };
console.log("Processing user: " + user);
// Output: "Processing user: [object Object]"
This is not a beginner mistake. This is a language feature working exactly as designed. And it has caused real bugs in production logging, data pipelines, and API responses.
Or take C macros:
#define SQUARE(x) x * x
int result = SQUARE(1 + 2); // Expands to: 1 + 2 * 1 + 2 = 5, not 9
The correct version wraps everything in parens. But nothing tells you this. You just have to know.
At Gerus-lab, when we evaluate a stack for a new project, we specifically audit what the language or framework allows by default. Can you accidentally mutate shared state? Can errors be silently ignored? Can type coercion hide a data bug?
The answers determine how much defensive code your team will write. And defensive code is expensive.
Trap #3: Names That Lie
This one is subtle but it accumulates cognitive load until your team is moving slow.
Postgres creates three databases on install: postgres, template0, template1. Not default, frozen_template, live_template. Just three names that require external documentation to understand.
Python has re.findall() for all matches and re.search() for a single match. Not re.find(). re.search(). Every time.
import re
# Find one match
match = re.search(r"\d+", "abc123") # Not re.find()
# Find all matches
matches = re.findall(r"\d+", "abc123def456")
These are not crises. But every time a developer hits one of these, they stop. They search. They lose context. Over a sprint, this adds up to hours.
When we onboard a new client project at Gerus-lab, one of our first moves is documenting these "gotcha" moments in the project's internal wiki. It sounds basic, but teams that skip this step re-learn the same lessons every three months.
Trap #4: Inconsistency as a Feature
Bash is the poster child here. In Bash:
-
ifcloses withfi. Makes sense, right? Thenforshould close with...done. Notrof. Justdone. -
${VAR:-default}sets a default. The-is not a minus. Do not ask why. -
&runs a process in background.&&is logical AND. One character, completely different behavior.
# This runs deploy in background, then runs tests regardless
deploy &
run_tests
# This runs tests ONLY IF deploy succeeds
deploy && run_tests
Mixing these up in a deployment script is a one-way ticket to an incident.
We are not saying avoid Bash. We are saying: if your team writes Bash, they need shellcheck, they need set -euo pipefail, and they need someone to review every script like it is application code. Because it is.
What We Actually Do About This
After 14 products, here is our practical answer:
1. Evaluate the trap density of any new tool before adopting it.
Before we added a new library to our AI agent project last year, we spent half a day reading its GitHub issues tagged "bug" and "unexpected behavior". The signal-to-noise ratio in those issues tells you everything.
2. Write defensive wrappers for known footguns.
We do not use raw exec() in Node. We wrap it. We do not use raw SQL string concatenation anywhere. We wrap it. Wrappers are cheap. Incidents are not.
3. Make the right thing the easy thing.
Project templates matter. If your starter template has set -euo pipefail, ESLint with strict rules, and TypeScript in strict mode — your developers will write safer code without thinking about it. The environment does the work.
4. Audit your stack like your stack is trying to hurt you.
Because sometimes it is. Not maliciously. But the designers of your tools made tradeoffs. Some of those tradeoffs aged badly. Know what they are.
The Bigger Picture
The best engineers I have worked with are not the ones who never make mistakes. They are the ones who build systems where mistakes are hard to make and easy to catch.
That distinction matters more when you are scaling. When you have 3 developers, you can catch footguns in code review. When you have 30, you need the tools themselves to push back.
At Gerus-lab, we have built products across Web3, AI, GameFi, and SaaS — and the pattern holds across all of them. Teams that fight their tools are slower, more buggy, and more burned out. Teams that choose tools deliberately, document the gotchas, and build defensive defaults ship faster and break less.
Stop blaming your developers. Start auditing your stack.
Need help building something where the architecture protects you instead of fighting you?
We have shipped 14+ products with this exact mindset — from Solana GameFi to AI automation to complex SaaS. If you are starting a new project or tired of firefighting on an existing one, let us talk.
Top comments (0)