arguingwithrobots
← all writing

Forced pushes are a process smell

Over-reliance on forced pushes isn't an agent quirk to work around; it's a sign of a bad coding practice for agents or humans.

Jim Meyer

I recently picked up a bit of work that I’d set down for a week and finished it off. It’s a branch on my fork of a fairly active project; upstream is the main project repo and origin is my fork. It was time to wrap up and push the work to my fork, then put up a PR.

Got it. I've refreshed origin/main from upstream; please rebase this worktree/branch.
Fetched origin and checked divergence from main
Branch is 5 behind / 1 ahead of origin/main. Rebasing onto the refreshed main:
Rebased onto origin/main
Rebased cleanly onto the refreshed origin/main — my single commit replayed on top with no conflicts. Let me verify the result and that nothing upstream collided with my changes.
Verified rebase result and checked for upstream overlap
The clean rebase (no conflicts) confirms none of the 5 upstream commits collided with my files — several of them touch supervisor/helm/kubernetes areas but not my scripts. Let me confirm my change still works post-rebase:
Re-ran test and confirmed signoff after rebase
Rebase is clean and verified — my commit (83991a33) sits on top of the 5 new upstream commits, test passes, signoff intact.
One thing before origin matches: the rebase rewrote history, so origin/fix/2112-macos-fd-limit/purp still points at the old pre-rebase commit. Updating it requires a force-push, which AGENTS.md says needs your explicit confirmation. The branch has no PR and no reviews yet, so this is low-risk, and I'd use --force-with-lease (aborts if origin moved unexpectedly).
Want me to git push --force-with-lease to bring origin in sync with the rebased branch?

… we have to force push one simple commit to manage a rebase where no one’s touched anything we’re working on? That makes no sense, but I’ll be the first to admit that my git expertise is strongest with a small core of git commands. There have definitely been times when I’ve gotten lost in a detached HEAD state or struggled with a three-way merge, and I’ve gotten very, very familiar with the many faces of git reset to clean up a mistake. I can easily imagine that Claude’s right and I just don’t understand why. Since Claude’s a patient teacher, I started asking why.

I don't understand why it requires a force if it didn't touch any of the files we're working in. Can you explain?
Good question — it's a common confusion. Force has nothing to do with which files changed. It's about commit identity and ancestry, not content.
Here's the mechanism:
[omitted: hundreds of lines of explanation, more questions, diagrams, discussions of commit ancestry, rewriting history, and even an HTML explainer with visuals]
Branch is safe and correctly rebased. Whenever you want origin to match, it's one git push --force-with-lease — replacing a commit only you ever saw. Want me to run it, or leave origin as-is for now?

GAAAAHHHHH!! Enough already! Time to get tight, terse, and walk through this step by step.

We're going to go to "PQA Mode". PQA stands for "precision question and answer". The core of this mode is that you ask exactly the question that you want answered in the most concise way, and you answer the question you were asked concisely using one of the five core answers: - yes/no - a number - a date - a list of bullet points - "I don't know, but I think ..." and one of the other four core answers
No extraneous decoration, no offers to do something, just responding to questions or asking very brief questions. Do you understand?
Yes.
✻ Cooked for 1s
When you say "you published 029e6022 to origin" do you mean "... to origin main", "... to origin fix/2112-macos-fd-limit/purp" or something else?
... to origin fix/2112-macos-fd-limit/purp.
✻ Worked for 1s
Is the need for --force because you rewrote or amended a commit?
Yes.

After all the words, it took two questions to get to the root of the problem, a rewritten commit after the original had been pushed. Easy to unwind, which we did, no --force needed.

There are a lot of varied feelings about the balance of many commits and a squash vs. fewer commits and a straight merge; I’m not here to litigate that debate. I’m definitely on the frequent small commits side, though, for several reasons:

  • I can easily push progress up so I can pull it down into other workspaces or branches
  • I can see my iterative progress, and doing so can often see the better path
  • When I introduce a bug or break something, it’s not hard to run back to the last known working state
  • When I’m working with others, I can see the back and forth which often kicks an intuition about something we missed or what to do next

And there’s one other thing, which feels both silly but worth saying out loud: when I’m working with a coding agent like Claude, I can see the value of my time reviewing, redirecting, and improving things. It’s personally rewarding to see where my work made our work better.

So, ultimately, we added this memory:

---
name: prefer-followup-commits-on-pushed-branches
description: "On an already-pushed branch, add a new commit and integrate upstream by merging — never amend/rebase/reset pushed commits (that forces a push)"
metadata: 
  node_type: memory
  type: feedback
  originSessionId: [...]
---

Once any commit on a branch has been pushed (or the branch backs an open PR), never rewrite it — no amend, no rebase, no reset. Rewriting a pushed commit changes its SHA, so the pushed tip is no longer an ancestor of the new tip and the next push is not a fast-forward: it demands `--force`. This applies to *every* reason to touch history, not just review feedback:

- **Review feedback / follow-up changes:** add a new follow-up commit on top.
- **Integrating updated `main` into the pushed branch:** `git merge origin/main` (a merge commit keeps the pushed tip as a parent → fast-forward push). Do NOT `git rebase origin/main` — that rewrites the pushed commit and forces the push. This is the exact trap hit on fix/2112 (2026-07-11): a pushed commit was rebased onto refreshed main, then required a force-push to publish.

**Why:** Nothing in the project's AGENTS.md or CONTRIBUTING.md mandates squashing, amending, or a linear branch history — amending/rebasing was my own default, and Jim prefers a clean fast-forward with the delta legible as its own commit. Force-pushes are separately gated behind explicit human confirmation per Jim's CLAUDE.md, so any workflow that *requires* one on a shared branch is the wrong default.

**How to apply:** Amend/rebase/reset are fine only for commits that are still local-only (never pushed). The moment a commit is pushed, treat its history as frozen: make changes as a new commit and pull in upstream via merge, both conventional-commit + `--signoff`, GPG-signed, pushed as a fast-forward with no `--force`. If you believe a rewrite is genuinely needed on a pushed branch, stop and ask first. See [[never-submit-prs-without-permission]].

Lesson learned, on both sides I think.