- ›Git Disasters and Process Debt
- Is Code Rotting Due To AI?
- The Integration Illusion
- When MCP Fails
- Context Engineering
- Stop Email Spoofing with DMARC
- SOTA Embedding Retrieval: Gemini + pgvector for Production Chat
- A Review of Agentic Design Patterns
- Building AI Agents for Automated Podcasts
- Rediscovering Cursor
- GraphRAG > Traditional Vector RAG
- Cultural Bias in LLMs
- Mapping out the AI Landscape with Topic Modelling
- Sustainable Cloud Computing: Carbon-Aware AI
- Defensive Technology for the Next Decade of AI
- Situational Awareness: The Decade Ahead
- Mechanistic Interpretability: A Survey
- Why I Left Ubuntu
- Multi-Agent Collaboration
- Building Better Retrieval Systems
- Building an Automated Newsletter-to-Summary Pipeline with Zapier AI Actions vs AWS SES & Lambda
- Local AI Image Generation
- Deploying a Distributed Ray Python Server with Kubernetes, EKS & KubeRay
- Making the Switch to Linux for Development
- Scaling Options Pricing with Ray
- The Async Worker Pool
- Browser Fingerprinting: Introducing My First NPM Package
- Reading Data from @socket.io/redis-emitter without Using a Socket.io Client
- Socket.io Middleware for Redux Store Integration
- Sharing TypeScript Code Between Microservices: A Guide Using Git Submodules
- Efficient Dataset Storage: Beyond CSVs
- Why I switched from Plain React to Next.js 13
- Deploy & Scale Socket.io Containers in ECS with Elasticache
- Implementing TOTP Authentication in Python using PyOTP
- Simplifying Lambda Layer ARNs and Creating Custom Layers in AWS
- TimeScaleDB Deployment: Docker Containers and EC2 Setup
- How to SSH into an EC2 Instance Using PuTTY
Git Disasters and Process Debt
This is a cautionary tale mixed with post-mortem solutions. When we grew the team, what started as a clean dev → staging → main
flow became a nightmare as we scaled from 2 to 6 developers.
Merge conflicts became my main job for an entire week. It did my head in. PRs ballooned to thousands of lines. Deployment velocity died as the team grew — the opposite of what you'd expect. We spent more time fighting Git than building features. Without regular production merges, changes accumulated until PRs became unreadable.
If merge conflicts are multiplying as your team grows, you need better patterns. This is how we broke Git—and the exact commands and workflows that fixed it.
What Went Wrong
Looking back, the problem is obvious. When you're building fast, these issues compound quietly until they explode.
The Spider Web Effect
Our Git graph showed the mess:
- Bidirectional merges:
dev → staging
, thenstaging → main
, thenmain → staging
- Out-of-sequence hotfixes: Emergency PRs bypassing the normal flow
- Long-lived branches: Feature branches living for weeks, accumulating conflicts
- Mixed merge strategies: Both merge and squash commits, preventing Git from consolidating history
We created a tangled web where branches diverged completely. Cherry-picks failed. Merge conflicts cascaded. Everything got worse.
Why This Mattered
We couldn't cherry-pick features to production because branches had no shared history.
Merge conflicts became impossible due to squash commits destroying shared history.
PRs became massive because fixing conflicts took so long that more changes accumulated, creating a vicious cycle.
Recovery Commands
After a week of untangling our Git mess, these are the commands that actually work.
See the Damage
# Get the full picture of where all branches stand
git fetch --all --prune
git branch -avv
git log --oneline --graph --decorate --all
# See the differences between branches
git diff main..staging --name-status
git diff staging..dev --name-status
Reset When Broken
# Reset to origin even during a rebase
git fetch origin
git checkout main
git reset --hard origin/main
# Find the last common ancestor
git merge-base main staging
# Hard reset to a specific commit (requires force-push)
git reset --hard <COMMIT_SHA>
git push --force-with-lease origin <branch>
Fix Conflicts
# Abort problematic operations
git merge --abort
git rebase --abort
# Cherry-pick with provenance tracking
git cherry-pick -x <COMMIT_SHA>
# Create throwaway branches for complex merges
git checkout -b merge-test main
git merge --no-ff origin/staging
Safe Conflict Resolution
The single most important pattern we learned: never resolve conflicts on protected branches.
- Create a throwaway branch from your target
- Merge the problematic branch into it
- Resolve conflicts safely in this sandbox
- PR the clean result back to target
- Delete the throwaway branch
Your protected branches stay clean. Conflicts become visible in PRs. No more force-pushing disasters.
One-Way Flow
The fix that eliminated 90% of our problems: never merge upstream.
feature/* → dev → main
The Rules
- Single directional promotion - No more
main → staging
orstaging → dev
merges. Justdev
andmain
- Short-lived feature branches - Maximum 3 days, branch off
dev
, merge back via PR, then delete - Squash and merge into dev only - Preserve clean history by avoiding squash merges on long-lived branches
- Merge commits from dev to main - Use merge commits (not squash) when promoting between long-lived branches
Hotfix Pattern
When production is on fire:
# Create hotfix off main
git checkout -b hotfix/1.2.3 main
# After merging hotfix → main, propagate back:
git checkout staging
git merge --no-ff main
git push origin staging
git checkout dev
git merge --no-ff main
git push origin dev
Branch Protection
GitHub settings that actually prevent disasters:
- Require PRs with up-to-date branches and passing CI
- Require linear history to prevent messy merge commits
- Auto-delete feature branches after merge
- Code owners for approval oversight
- Squash and merge into dev only - disable rebase merge entirely
Critical detail: use different merge strategies for different branches. Squash features into dev, but use merge commits when promoting to main. This preserves history where it matters.
Faster Reviews
We eliminated context switching with the GitHub Pull Requests and Issues VS Code extension:
- Review PRs without leaving your editor
- Test branches locally with one click
- Comment inline while coding
- Merge directly from VS Code
Review time dropped by 40%.
The Workflow
What actually works in practice:
- Create feature branch off dev with descriptive name
- Work in small increments - keep branches under 3 days
- Open PR to dev - squash and merge, auto-delete branch
- Nightly dev → main promotion - fast, regular deployments via merge commits
- Feature flags for incomplete work - deploy safely without exposing unfinished features
Merge Standards
Code gets merged when:
- Functionality must be testable and non-detrimental to existing features
- Backwards compatibility maintained
- Modular architecture - new features in separate folders/routes
- Focus reviews on logic and architecture - ignore formatting nits
Process Debt Compounds
Our Git disaster wasn't just about bad merge strategies. It was process debt accumulating interest:
The signals we missed:
- PR size exploded from ~200 lines to 2000+ lines
- Deployment frequency dropped from daily to weekly
- New developers took a lot of time to understand our "workflow"
- Code review became rubber-stamping because PRs were unreadable
Each shortcut compounded. Skip documentation? New devs create their own workflows. Allow any merge strategy? Git history becomes unsearchable. Delay conflict resolution? PRs become unreviewable.
The real cost: That week I spent untangling Git? Multiply by 6 developers. Add the bugs that slipped through massive PRs. Add the features we didn't ship. Process debt doesn't just slow you down — it stops you cold.
The Lessons
- Constraints beat freedom. Complete Git autonomy creates chaos. Clear rules prevent problems.
- Merge strategy matters. Squash merges on long-lived branches destroy history and create conflicts.
- Protected branches need special care. You can't force-push out of problems. Use proper workflows.
- Promote regularly. Daily or weekly promotion prevents massive, unreviewable PRs.
- Document everything. Without clear processes, everyone invents their own workflow.
- Watch the metrics. PR size and deployment frequency are your canaries in the coal mine.
The Takeaway
Scaling from 2 to 6 developers shouldn't break your Git workflow. But it will if you don't evolve your processes.
The fix wasn't complex—it was ruthlessly simple. One-way flow. Protected branches. Clear merge strategies. Most importantly: treat process improvements as real work, not something you'll "get to later."
Process debt compounds faster than technical debt. Fix it before it fixes you.