TLDR: If you fork third-party repos and patch CVEs in them, do NOT push to the upstream. Mirror to your own private repo first — and watch out for shallow clones, which will break in a very confusing way.

The Setup

I'd just run a full supply-chain security audit across every project I run for an ecommerce business and my own MCP servers (MCP = Model Context Protocol, the tool-use layer for my AI agent Apollo).

Patched CVEs in nine repos in a single session. It felt great.

Then I said: "Push across the board."

Simple enough. Until it wasn't.

The Wall I Hit

Two repos weren't mine.

My Things MCP integration — my task manager integration — had originally been cloned from the upstream Things MCP repository on GitHub. Its origin still pointed there.

Same story with my QuickBooks MCP server, cloned from the official upstream repository.

I'd patched them locally. But I couldn't push security fixes to repos I don't own. And even if permissions let me try, pushing my private patches to someone else's upstream would be the wrong move.

So the rule became: check git remote -v on every repo before touching push. Classify each one — do I own the origin, is it a co-dev canonical, or is it a third-party upstream I cloned from? They each need a different answer.

The Mirror Pattern (and Where It Broke)

For third-party origins, the move is:

  1. git remote rename origin upstream
  2. gh repo create <your-account>/<name> --private --source=. --remote=origin --push

That creates a fresh private mirror under my GitHub account and pushes in one shot.

Except…

remote: fatal: did not receive expected object
remote unpack failed: index-pack failed

That error means the repo is a shallow clone — git only fetched recent history when I originally cloned it, not the full object graph. GitHub can't receive a push from an incomplete history.

Diagnosis: git rev-parse --is-shallow-repositorytrue.

Fix: git fetch --unshallow upstream to backfill everything, then git push -u origin HEAD.

Two new private repos — a private mirror of my Things MCP integration and a private mirror of my QuickBooks MCP server — both clean, both version-controlled for the first time, both carrying the CVE patches.

One More Gotcha (Embarrassing but True)

I also managed to commit a uv.lock.bak file because I hadn't put *.bak in .gitignore.

Lock backups. Not secrets. But that's exactly the kind of noise that slips through when you're pushing fast across nine repos at once.

Had to do the walk of shame: git rm --cached *.bak, recommit, repush.

Add *.bak to your .gitignore before you need to.

Why This Matters

The goal of all of this was to make sure security patches stay mine until I decide where they go — and that nothing slips out in a push that shouldn't.

The lesson isn't complicated: before any broad push, classify your remotes. Own it → push. Third-party upstream → mirror first. Shared canonical → ask. No remote → flag it.

And if a repo was git clone'd from someone else's GitHub, assume it's shallow until proven otherwise.

P.S. The four MCP servers — my Things MCP integration, my QuickBooks MCP server, my local Arcade MCP server, and my Signal MCP server — had never been version-controlled at all before this session. They live at ~/Developer/MCP Servers/ and were just… local directories. That's fixed now.