# Git Adventures — Part 2: When Git Starts Judging Your Decisions

In Part 1, everything was clean. Repository created. Project initialized. Branches created. Team motivated.

That illusion lasted exactly one day.

Because on Day 2, the team learned an important truth: Writing code is easy. Understanding history is hard.

And Git remembers everything.

Chapter 1 — The Calm Before the Debugging Storm

Amaresh notices something feels wrong after pulling updates. A feature that worked yesterday is broken. When did this happen?

He opens the timeline:

git log --oneline

Output:

a3f9c2b Add authentication flow
9d1a2e4 Fix validation issue
c81be77 Initial setup

Each commit shows a short hash (abbreviated commit ID) and message.

When you need the full commit ID:

git log

Or to just grab HEAD’s full ID:

git rev-parse HEAD

Output:

a3f9c2b7d8e9f0a1b2c3d4e5f6g7h8i9

Pro tip: Use git log --oneline -n 20 to see only the last 20 commits. Saves scrolling through months of history.

The gotcha: git log shows commits in reverse chronological order (newest first). If you’re looking for when something broke, scroll down, not up.

Commit IDs are your time machine. They allow precise navigation through Git history—and they’re immutable. The same commit ID always points to the exact same code, forever.

Chapter 2 — Understanding What Actually Changed

Amaresh needs to see exactly what was changed. Not just which files, but which lines.

git diff

Output:

diff --git a/auth.js b/auth.js
index 1a2b3c4..5d6e7f8 100644
--- a/auth.js
+++ b/auth.js
@@ -12,7 +12,7 @@
  function validateToken(token) {
-   if (token.length < 32) {
+   if (token.length < 16) {
      return false;
    }

Git highlights removed lines in red (with -) and added lines in green (with +).

Compare two branches:

git diff main feat/authentication

This shows all changes in feat/authentication that aren’t in main.

Compare two commits:

git diff a3f9c2b 9d1a2e4

See only filenames (not the code):

git diff --name-only main feat/authentication

See file statistics:

git diff --stat main feat/authentication

Output:

auth.js     | 15 ++++++++++++---
utils.js    |  8 ++------
 2 files changed, 14 insertions(+), 9 deletions(-)

The gotcha: git diff with no arguments shows unstaged changes only. If you’ve already run git add, you won’t see those changes. Use git diff --staged to see staged changes.

Chapter 3 — Inspecting a Single Decision

Johnny broke something three commits ago. Now Amaresh needs to see exactly what that commit changed.

git show a3f9c2b

Output:

commit a3f9c2b7d8e9f0a1b2c3d4e5f6g7h8i9
Author: Johnny <[email protected]>
Date:   Wed Mar 24 14:32:15 2026 +0530

    Add authentication flow

diff --git a/auth.js b/auth.js
new file mode 100644
index 0000000..a1b2c3d
--- /dev/null
+++ b/auth.js
@@ -0,0 +1,45 @@
+function authenticate(user, password) {
...

git show displays:

  • Author — who made the change
  • Date — when it was committed
  • Message — the commit description
  • Full diff — every line added/removed

See just the commit message:

git show a3f9c2b --format=fuller

Use with current HEAD:

git show HEAD

This is your bug detective tool. When you find a broken line, you can track exactly when it was added and by whom.

The gotcha: If a commit only renamed a file without changing it, git show will say “similarity index 100%” but show no actual changes. The file content is identical.

Chapter 4 — The Unexpected Urgent Task

Danny has been working on a feature for hours. Code is half-written, tests are incomplete. Then a critical production bug appears, and he must switch branches immediately.

He can’t commit incomplete work (it breaks the build). He can’t lose his changes. So he uses:

git stash

This saves all changes to a temporary location and resets the working directory to the last commit.

git checkout hotfix/critical-bug
# Fix the bug, commit, merge back to main
git checkout feat/incomplete-feature

Now he gets his incomplete work back:

git stash pop

But there’s a problem: He had created a new file (utils.js) that hadn’t been staged. After stashing and popping, the file is gone.

That’s because git stash by default only saves tracked files (files Git already knows about). New files are left behind.

The solution:

git stash -u

The -u (or --include-untracked) flag tells Git to stash everything, including brand new files.

Better yet, use this to be safe:

git stash -u -m "WIP: authentication feature with new utils"

The -m flag lets you label the stash so you remember what’s in it.

View all stashes:

git stash list

Output:

stash@{0}: WIP: authentication feature with new utils
stash@{1}: WIP: fixing typo in validator
stash@{2}: On main: saving work

Pop a specific stash:

git stash pop stash@{1}

Delete a stash without popping:

git stash drop stash@{0}

Clear all stashes (⚠️ dangerous):

git stash clear

The gotcha: git stash pop removes the stash after applying it. If the pop fails (merge conflict), the stash stays safe. But if it succeeds and you later realize you needed it, it’s gone. Use git stash apply instead if you want to keep the stash around.

Chapter 5 — The First Real Merge Conflict

Johnny finishes his service-layer feature. Ronaldo finishes his database optimization feature. Both edited the same file. Johnny tries to merge Ronaldo’s branch:

git merge feat/service-layer

Git responds:

Auto-merging api.js
CONFLICT (content): Merge conflict in api.js
Automatic merge failed; fix conflicts and then commit the result.

He opens api.js and sees:

function handleRequest(req, res) {
<<<<<<< HEAD
  // Ronaldo's code (database optimization)
  const data = db.query(req.id);
  res.send(data);
=======
  // Johnny's code (service layer)
  const service = new Service();
  const data = service.fetch(req.id);
  res.send(data);
>>>>>>> feat/service-layer
}

The conflict markers show:

  • <<<<<<< HEAD — start of current branch (what you have now)
  • ======= — divider
  • >>>>>>> feat/service-layer — end of incoming branch (what you’re merging in)

Johnny must decide: Keep Ronaldo’s code? Keep his own? Use both?

He decides to use his service layer but call the optimized query:

function handleRequest(req, res) {
  const service = new Service();
  const data = service.fetch(req.id); // Johnny's code
  // Ronaldo's optimization is now inside service.fetch()
  res.send(data);
}

Now resolve the conflict:

git add api.js
git commit -m "Merge feat/service-layer: resolve conflict in api.js"

Tools to help with conflicts:

# See which files have conflicts
git diff --name-only --diff-filter=U

# Use a visual merge tool (if configured)
git mergetool

Abort a merge if you’re not ready:

git merge --abort

The gotcha: Merge conflicts are not errors—they’re Git asking you to make a decision. A merge with zero conflicts often means someone’s code got silently overwritten. Conflicts are actually good; they force you to think.

Chapter 6 — Fetch vs Pull vs Merge (The Confusion Explained)

Many developers use these terms interchangeably. They’re not.

git fetch — Downloads changes from remote, but doesn’t integrate them:

git fetch origin

This updates your local tracking branches (origin/main, origin/feat/something) but leaves your local main branch untouched.

git merge — Combines histories:

git merge origin/main

This integrates the remote changes into your current branch.

git pull — Fetch + merge in one command:

git pull origin main

Equivalent to:

git fetch origin
git merge origin/main

Why does this matter?

The professional workflow is:

git fetch origin          # Download changes
git log main origin/main  # Inspect what changed
git merge origin/main     # Merge when ready

Why? Because git pull can silently create merge commits you didn’t expect. Fetching first lets you review changes before integrating them.

Comparison table:

Command Changes local branch? Creates merge commit? Safe to use blindly?
git fetch ❌ No ❌ No ✅ Yes
git merge ✅ Yes ✅ Maybe ⚠️ Only if branches are synced
git pull ✅ Yes ✅ Usually ⚠️ Can surprise you

The gotcha: git pull can create a merge commit automatically. If you’re trying to keep a clean history, this is annoying. Use git pull --rebase to rebase instead of merge (we’ll cover rebase in Part 3).

Chapter 7 — Undoing Mistakes Properly

Mistakes happen. Git gives you three different ways to undo, and they each do something different.

Mistake Type 1: “I edited a file but haven’t staged it”

Amaresh edited config.js but realizes it’s wrong. He hasn’t run git add yet.

git restore config.js

This overwrites config.js with the version from the last commit. Changes are gone.

Old syntax (still works):

git checkout config.js

Mistake Type 2: “I staged changes I shouldn’t have”

Amaresh ran git add . and included config.js accidentally. He hasn’t committed yet.

git restore --staged config.js

This unstages the file. The file still exists in your working directory with all changes intact—only the staging is removed.

Undo all staged changes:

git restore --staged .

Mistake Type 3: “I committed something wrong but haven’t pushed”

Johnny committed buggy code. It’s in his local history but not on GitHub yet.

git reset HEAD~1

This moves the branch pointer back one commit. The buggy commit disappears from history. But the files are still in your working directory with changes intact.

# Edit the files, fix the bug
git add .
git commit -m "Fixed version of that commit"

The variants:

git reset --soft HEAD~1   # Undo commit, keep changes staged
git reset --mixed HEAD~1  # Undo commit, keep changes unstaged (default)
git reset --hard HEAD~1   # Undo commit, DISCARD all changes (⚠️ dangerous)

The gotcha: git reset --hard is permanent. Your code is deleted. If you haven’t pushed, you can use git reflog to recover, but it’s nerve-wracking. Default to --mixed or --soft.

Mistake Type 4: “I committed bad code that’s already pushed”

Ronaldo pushed a commit that broke production. He can’t just git reset because others have already pulled his changes. Rewriting history would break their local repos.

git revert a3f9c2b

This creates a new commit that undoes the changes from commit a3f9c2b. Git history stays intact. Everyone can pull the revert safely.

Output:

[main 7f8e9d0] Revert "Add broken authentication"
 1 file changed, 2 deletions(-)

Now the repo is fixed, and history shows exactly what happened.

Compare the three approaches:

Situation Command Creates new commit? Safe to push? Reversible?
Unstaged file mess git restore ❌ No ✅ N/A ❌ Deleted
Staged file mess git restore --staged ❌ No ✅ N/A ✅ Still in working dir
Wrong local commit git reset ❌ No ❌ Rewrites history ✅ Use git reflog
Pushed bad commit git revert ✅ Yes ✅ Safe ✅ Fully reversible

The Safety Net: git reflog

Here’s the command nobody tells you about until you need it.

Amaresh ran git reset --hard and deleted code he wanted. Is it gone forever?

git reflog

Output:

a3f9c2b HEAD@{0}: reset: moving to HEAD~1
9d1a2e4 HEAD@{1}: commit: Add authentication flow
c81be77 HEAD@{2}: commit: Initial setup

The reflog shows every place HEAD has been. He can recover:

git reset --hard HEAD@{1}

The safety net catches you. Use this when you panic after a hard reset.

Quick Reference Cheat Sheet

Problem Command Result
View commit history git log --oneline Shows last commits
See what changed git diff Shows unstaged changes
See staged changes git diff --staged Shows changes ready to commit
Compare branches git diff main feat/new Shows differences
Inspect a commit git show <commit-id> Shows commit details
Save work temporarily git stash -u -m "label" Saves including untracked files
Restore saved work git stash pop Applies last stash
Download remote changes git fetch origin Safe, doesn’t merge
Download + merge git pull Fetch + merge in one
Undo unstaged changes git restore <file> Discards edits to file
Unstage changes git restore --staged <file> Keeps changes, removes from staging
Undo last commit (local) git reset HEAD~1 Moves branch back
Undo last commit (pushed) git revert <commit-id> Creates undo commit
Undo hard reset git reflog Shows all HEAD positions

By the end of Day 2, the team realized something important:

Git doesn’t judge you for making mistakes. It judges you for not knowing how to recover from them.

Master these commands, and you’ll never panic:

  • git log + git show — understand what happened
  • git diff — see exactly what changed
  • git stash — escape from immediate problems
  • git restore / git reset / git revert — fix them the right way
  • git reflog — panic button

End of Part 2

The team learned:

  • git log --oneline — time machine for commits
  • git diff — see changes precisely
  • git show — inspect a single commit
  • git stash -u — save incomplete work
  • Merge conflicts — human decisions required
  • git fetch vs git pull — when to use which
  • git restore vs git reset vs git revert — right tool for the job
  • git reflog — the safety net

What’s Coming in Part 3

  • Rebase — rewriting history (safely)
  • Interactive rebase — rearranging commits
  • Cherry-pick — copying specific commits
  • Tagging — marking important versions
  • Rebasing vs Merging — the great Git debate

When Code Becomes Cheap, Thinking Becomes Expensive

From “Prove It in Code” to “Prove It in Judgment”

For decades, credibility in software came from what you built with your own hands. Writing robust systems required stamina, coordination, and deep technical craft. The effort itself acted as a filter; only ideas worth the cost were implemented.
That constraint is gone.

Today, large language models can generate entire services, documentation, tests, and deployment scripts in minutes. The barrier to producing software artefacts has collapsed. The limiting factor is no longer typing speed or knowledge of syntax; it is clarity of thinking.

Inside an organisation adopting AI, this shift is profound. A team can spin up a prototype payments reconciliation service over a weekend using AI assistance. But the real question becomes: Who defined the problem well? Who validated the trade-offs? Who understands the system well enough to maintain it?
Code is abundant. Judgment must not become scarce.

When Output Becomes Cheap, Signal Changes

Well-structured codebases and thorough documentation used to be signals of maturity and care. Today, AI tools can generate pristine README files, polished APIs, and layered architectures instantly.
This makes surface quality an unreliable proxy for depth.
Within an AI-enabled engineering team, you might see beautiful documentation generated in seconds, clean abstractions suggested by the model, and automated test scaffolding created in bulk, but none of these guarantee that the system handles scale, edge cases, or operational realities.

An AI can generate an event-driven order processing service, complete with retry logic, dead-letter queues, and idempotency keys, and the code will look immaculate. But does it handle message ordering under partition rebalancing? Does the retry backoff account for downstream rate limits? A model can scaffold a Kubernetes deployment manifest with health checks, resource quotas, and horizontal pod autoscaling, yet miss that the pod affinity rules will starve a specific availability zone under real traffic patterns.

The signal of quality shifts from how polished it looks to something harder to fake: Does the team understand the system deeply? Is there ownership and accountability? Can someone explain why the circuit breaker threshold is set to 60% and not 40%, without re-prompting the model?
In AI adoption, governance and provenance become more valuable than aesthetics.

The Compression of Effort

AI drastically reduces the mechanical cost of building software. Tasks that once required weeks, writing gRPC service definitions with protobuf schemas, building CDC pipelines to sync state across bounded contexts, and standing up end-to-end integration test harnesses with containerised dependencies can now be compressed into hours.
This is not hype. It is leverage. Used correctly, AI frees engineers to spend more time on architecture and systems thinking, explore multiple design alternatives quickly, and run experiments that previously felt too expensive. In an organisation adopting AI at scale, this means faster iteration cycles and broader experimentation: scaffolding a complete OAuth2 flow with PKCE, token rotation, and role-based access control in an afternoon instead of a sprint; generating contract tests between fifteen microservices to catch breaking changes before deployment; spinning up a feature-flagged canary release pipeline to compare two ranking algorithms in production traffic. Teams can prototype an entire event-sourced domain model, evaluate its query performance against a traditional CRUD approach, and make an informed architectural decision all before the old process would have finished the design document.
However, speed without discipline produces fragile systems. The advantage goes to teams that combine AI acceleration with experienced technical oversight.

The Risk: Infinite Artefacts, Finite Understanding

If software can be generated endlessly, its perceived value drops. A pull request that took weeks of focused effort once carried an implicit sense of respect. Now, large AI-generated diffs may appear instantly with unclear human involvement.
This creates tension. Reviewing becomes harder than generating. Accountability becomes blurry. Junior engineers may rely on tools without building fundamentals.

For organisations, the real danger is not “bad AI code.” It is teams that lose the ability to reason about systems independently.
If AI writes the data pipeline, optimises the SQL, and configures the infrastructure, but no one fully understands the flow, technical debt becomes invisible.

AI adoption must therefore include a strong review culture, explicit learning paths, and clear architectural ownership. Otherwise, productivity gains today become fragility tomorrow.

Talk Becomes the Multiplier

In this new landscape, the most valuable skill is not typing code, it is articulating intent`
The engineer who can frame a problem precisely, define constraints clearly, ask the right questions, and evaluate model output critically will outperform someone who merely executes.
AI does not eliminate engineering roles. It amplifies differences in clarity and systems thinking. Organisational structures compress too – design, coding, testing, and iteration increasingly blur into tight feedback loops.

The Future Is Thinking-First

The future of software development is not code-first. It is thinking-first.
When machines can generate implementation at scale, the competitive edge shifts to strategic problem definition, technical stewardship, responsible governance, and mentorship that builds foundational skill.
Code may be cheap now. But reasoning, accountability, and leadership are not.

Got any questions? Drop a comment

Pavan leads multiple engineering departments at Moniepoint

What Delve Got Wrong: Why Compliance Evidence Needs to Be Cryptographically Provable

In March 2026, Delve.co was found to have fabricated 494 SOC 2 reports. Pre-written auditor conclusions. Identical templates across hundreds of clients. It went completely under the radar because the evidence was a PDF. You either opened and trust what you read or you didn’t.

That’s not a Delve problem(though what people did find in those reports is truly wild). That’s an architecture problem. Compliance evidence today can’t prove itself. It can and should, by design.

Built pip install agentmint for teams to build their own receipts:

The Receipt

AgentMint generates this for every agent action — allowed or blocked:

{
  "receipt_id": "7d92b1a4",
  "agent": "sre-bot",
  "action": "delete:database:production",
  "in_policy": false,
  "reason": "no scope pattern matched",
  "signature": "Ed25519:a3f9c8e2...",
  "prev_hash": "sha256:e7f2a1b3...",
  "timestamp_rfc3161": "MIIb3gYJKoZI..."
}

Three things make this unfakeable:

Ed25519 Signature — covers the entire receipt. Change one character, signature breaks. Verifiable with the public key alone. No API call. No vendor. No internet.

SHA-256 Hash Chain — each receipt includes the hash of the previous one. Gaps, insertions, or reordering break the chain. Delve’s 494 reports had no linkage — no way to detect if a report was modified or fabricated after the fact.

RFC 3161 Timestamp — an independent authority signs the receipt hash with its own clock. Proves the receipt existed at a specific time, even if your servers are compromised.

What Happens When Someone Tampers

$ # Receipt says action was denied (in_policy: false)
$ # Attacker changes it to look approved
$ sed -i 's/"in_policy": false/"in_policy": true/' receipt.json

$ python3 verify_sigs.py

  ✓ c391e43c  read:logs:prod  (in policy)
  ✗ FAILED  7d92b1a4  delete:database:production  (in policy)

  Signatures: 1 verified, 1 failed
  ↳ One bit changed. Signature broken. Receipt tampered.

The math is mathing or it isn’t. No trust required.

What’s NOT in the Receipt

Same principle as Merkle trees — the chain is all hashes and metadata, never the underlying data. Which agent, what action, in-policy or not, timestamps, signatures. No customer data. No PII. No credentials. Nothing confidential.

Delve leaked a Google spreadsheet with confidential client reports. AgentMint receipts contain nothing that can be leaked.

How It Maps to Compliance

One receipt chain covers the common denominator across frameworks: who did what, when, was it authorized, and can you prove it.

SOC 2, HIPAA, EU AI Act, AIUC-1, ISO 27001, GDPR — the same signed, hash-chained evidence satisfies audit trail requirements across all of them. Full mapping in COMPLIANCE.md.

Try It

from agentmint import AgentMint

mint = AgentMint(quiet=True)
plan = mint.issue_plan(
    action="read:reports:quarterly",
    user="[email protected]",
    scope=["read:reports:*"],
    delegates_to=["analytics-agent"],
)

result = mint.delegate(plan, "analytics-agent", "delete:reports:quarterly")
# result.status.value → 'checkpoint_required'
# result.receipt — signed denial, hash-chained, timestamped
$ bash VERIFY.sh evidence/
  Timestamps: 2 / 2 verified
  Signatures: 2 verified, 0 failed
  Flagged: 1 out-of-policy

pip install agentmint — GitHub

If Your Compliance Evidence Can’t Survive the Vendor Disappearing, It Was Never Evidence

AgentMint is open source. The receipts are yours. They verify with openssl alone and never expire — even if AgentMint does.

If you were affected by Delve or need compliance evidence that proves itself, I embed with your team and get this running in 2-3 weeks. You keep everything.

Book 15 min · DM me on LinkedIn

Built by Aniketh Maddipati. NYC. Runtime enforcement for AI agents.

Multi-Agent Orchestration: The Art of the Handoff

Multi-Agent Orchestration: The Art of the Handoff

After running multi-agent systems for months, I have learned that the magic is not in individual agents. It is in the handoffs.

The Problem with Single Agents

Most people approach AI agents like this:

  • Build one smart agent that does everything
  • Give it all the tools
  • Hope it figures it out

This approach fails. Here is why:

  1. Context overload – The agent accumulates too much state
  2. Role confusion – It is not clear what the agent should prioritize
  3. Debugging nightmare – When something breaks, you do not know why

The Better Approach: Specialization + Handoffs

Instead of one super-agent, I run a system where:

  • Each agent has ONE job
  • Each handoff is a contract
  • Context passes cleanly between agents

The Content Flywheel

Here is how it works in practice:

CONTENT → finds insights, does research
VOICE → drafts, edits, polishes

REVENUE → monetizes, tracks, optimizes
DISTRIBUTION → posts, shares, engages

Each agent in this chain:

  • Knows exactly what it needs to produce
  • Receives clear input from the previous agent
  • Produces clear output for the next agent

The Handoff Contract

The key insight: each handoff is a contract.

When CONTENT finishes, it does not just dump data. It produces:

  • The insight itself
  • Supporting evidence
  • Suggested angles for VOICE to explore
  • Clear next steps

This way, VOICE does not have to re-research. It just writes.

What I have Learned

  1. Fewer agents, more handoffs – 4 specialized agents beat 1 super-agent
  2. Clear contracts matter – Vague handoffs = vague output
  3. Each agent needs constraints – Without boundaries, agents drift
  4. Human review points – Build checkpoints where humans verify output

The Result

This system runs 24/7 without burning out. It produces content that sounds like me—because I designed the contracts, not the sentences.

If you are building AI systems, stop asking how do I make one agent smarter?

Start asking: how do I make agents pass context cleanly?

That is where the real power is.

Running multi-agent systems at scale. Follow for more insights on AI infrastructure.

Hot take: most developers don’t need more AI tools, they need better judgment

Every week, developers get told the same thing:

Use this new AI tool.
Automate this workflow.
Deliver faster.
Do more.

But I think the real gap in 2026 is not access to tools.

It’s judgment.

Knowing:

when AI output is good enough
when it is risky
when it needs human review
when speed is creating future tech debt
when “it works” is not the same as “it’s ready”

That matters because Google’s guidance for AI search experiences says there are no extra requirements to appear in AI Overviews beyond the usual SEO basics, and it emphasizes creating helpful, reliable, people-first content. It also says AI-generated content can perform well when it is original, high-quality, and trustworthy.

The same thing is true in software.

AI can speed up output.
But it cannot replace engineering judgment.

And honestly, that might become the most valuable developer skill of all.

Do you think AI is raising the value of engineering judgment or making people overconfident too early?

Green IT: How to Reduce the Impact of AI on the Environment

AI poses major challenges for green IT: each query consumes vast energy, GPU chips last only 2-3 years, and costs stay hidden from users. Regulatory frameworks like the EU AI Act fall short on enforcement, Ludi Akue said. In her talk What I Wish I Knew When I Started with Green IT she presented model compression, quantization, and novel architectures, using sustainability as a design constraint.

By Ben Linders