Skip to content

rule(B-0746)+backlog: GitHub auto-closes PR on force-push to base-SHA + refuses reopen — industry-wide trap (bit Zeta on #4997 + ServiceTitan per Aaron)#5011

Closed
AceHack wants to merge 1 commit into
mainfrom
rule/github-pr-auto-closes-on-force-push-b0746-aaron-2026-05-25
Closed

rule(B-0746)+backlog: GitHub auto-closes PR on force-push to base-SHA + refuses reopen — industry-wide trap (bit Zeta on #4997 + ServiceTitan per Aaron)#5011
AceHack wants to merge 1 commit into
mainfrom
rule/github-pr-auto-closes-on-force-push-b0746-aaron-2026-05-25

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented May 25, 2026

Summary

Aaron 2026-05-25, after I hit the trap on PR #4997 squashing via force-push:

"save that lesson it's not obvious even to human devs it's bit us too at ServiceTitan"

The trap shape

Squashing a PR's branch via force-push must NEVER land the branch ON the base ref's SHA, even briefly. GitHub treats head == base as terminal (no diff = no PR) + auto-closes; the reopenPullRequest GraphQL mutation refuses the reopen even after the head ref is restored to a diff-having SHA.

Two correct patterns documented

Pattern A (recommended): Cherry-pick onto fresh branch + open new PR. Avoids the trap entirely; old branch never modified.

Pattern B: Verify HEAD-moved before push:

HEAD_BEFORE=$(git rev-parse origin/main)
HEAD_AFTER=$(git rev-parse HEAD)
if [ "$HEAD_BEFORE" = "$HEAD_AFTER" ]; then
  echo "FATAL: commit didn't move HEAD; would push base SHA + auto-close PR"
  exit 1
fi
git push origin HEAD:refs/heads/<pr-branch> --force-with-lease

--force-with-lease clarification

Per Aaron's standing authorization: --force-with-lease is safe + authorized. The trap is NOT force-push being destructive; it's the SPECIFIC failure mode of pushing a base-SHA to a PR branch.

Empirical anchor

2026-05-25 session:

  1. PR feat(B-0737): zflash — Touch ID PAM as irreversible-action consent gate + short 'yes <4-hex>' challenge + ISO auto-discovery ('I execute, you fingerprint') #4997 had 7 review threads needing addressing
  2. Squash attempt: worktree on origin/main, cumulative diff applied, git commit ran
  3. Tail-3 output captured HEREDOC close but not commit confirmation
  4. git rev-parse HEAD returned origin/main's SHA — silent commit failure OR rev-parse misread
  5. Pushed base-SHA via --force-with-lease (succeeded; remote branch went to no-diff state)
  6. GitHub auto-closed PR feat(B-0737): zflash — Touch ID PAM as irreversible-action consent gate + short 'yes <4-hex>' challenge + ISO auto-discovery ('I execute, you fingerprint') #4997
  7. gh pr reopen 4997 refused via GraphQL even after head ref restored
  8. Recovery: fresh branch + fresh PR feat(B-0737): zflash + Touch ID PAM + short challenge + ISO auto-discovery — 'I execute, you fingerprint' (carry-over from #4997) #5010 + cross-link comment on closed feat(B-0737): zflash — Touch ID PAM as irreversible-action consent gate + short 'yes <4-hex>' challenge + ISO auto-discovery ('I execute, you fingerprint') #4997

Industry-wide

Aaron's disclosure: "it's bit us too at ServiceTitan" — not Zeta-specific. Any team using GitHub PRs + occasionally force-pushing for squash + having automation that could silently fail at commit step is susceptible.

Composes with

Test plan

  • Rule auto-loads at cold-boot (.claude/rules/<name>.md matches loading-taxonomy)
  • B-0746 row composes with B-0737 + related rules
  • Empirical anchor preserved in both rule body + row body (verifiable in git history)
  • BACKLOG.md regenerated
  • No code changes; substrate-engineering lesson-landing only

🤖 Generated with Claude Code

… + refuses reopen — industry-wide trap (bit Zeta on #4997 + ServiceTitan per Aaron)

Aaron 2026-05-25, after I hit the trap on PR #4997 squashing via
force-push: 'save that lesson it's not obvious even to human devs
it's bit us too at ServiceTitan.'

Trap shape: squashing a PR's branch via force-push must NEVER land
the branch ON the base ref's SHA, even briefly. GitHub treats
'head==base' as terminal (no diff = no PR) + auto-closes; the
reopenPullRequest GraphQL mutation refuses the reopen even after
the head ref is restored to a diff-having SHA.

Two correct patterns documented:
- Pattern A (recommended): cherry-pick onto fresh branch + open new
  PR; old branch never modified
- Pattern B: verify HEAD-moved-before-pushing via pre-push check

Clarifies that --force-with-lease IS Aaron-authorized + safe in itself
(per his 2026-05-25 'force least is always fine i never look at it is
destructive' standing authorization); the trap is the SPECIFIC failure
mode of pushing a base SHA, not force-push.

Empirical anchor: 2026-05-25 session PR #4997 → squash attempt → silent
commit failure OR rev-parse misread → pushed base SHA → GitHub
auto-closed → reopen refused → substrate carried over to fresh
PR #5010 + cross-link comment on closed #4997.

Industry-wide per Aaron's ServiceTitan disclosure; rule landing in
.claude/rules/ ensures future-Otto + future-AI + (transitively)
future human contributors inherit the trap-avoidance at cold-boot.

Composes with B-0737 (the substrate that was on the trapped PR) +
B-0741 fork framing (rule travels to any fork that adopts the rule
library — concrete value of 'AI-native project adopts Ace conventions
for free').

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 25, 2026 22:28
@AceHack AceHack enabled auto-merge (squash) May 25, 2026 22:28
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR lands an operational “rule + backlog row” documenting a GitHub PR state-machine trap: if a force-push briefly makes a PR’s head SHA equal the base SHA (no diff), GitHub auto-closes the PR and (per the write-up) refuses reopening even after restoring the head SHA. It captures an empirical anchor (PR #4997#5010) and regenerates the backlog index.

Changes:

  • Added a new .claude/rules/ rule describing the trap and recommended mitigation patterns.
  • Added backlog row B-0746 documenting the lesson + empirical anchor.
  • Updated docs/BACKLOG.md to include B-0746.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
.claude/rules/github-pr-auto-closes-on-force-push-to-base-sha-refuses-reopen.md New operational rule describing the trap + mitigation patterns.
docs/backlog/P2/B-0746-github-pr-auto-closes-on-force-push-to-no-diff-state-irreversibly-rule-landing-empirical-anchor-pr-4997-to-pr-5010-aaron-2026-05-25.md New P2 backlog row capturing the lesson and empirical anchor.
docs/BACKLOG.md Regenerated index entry adding B-0746.

Comment on lines +86 to +90
## Pattern C — `--force-with-lease` is Aaron-authorized + safe in itself

Per Aaron 2026-05-25: *"force least is always fine i never look at it is destructive"* — `--force-with-lease` is the safe form of force-push (refuses if the remote SHA isn't what you expected, which catches concurrent pushes from peer agents). Aaron explicitly authorized it.

The classifier wrongly blocked `--force-with-lease` as destructive in one session; clarification: Aaron's standing authorization covers `--force-with-lease`. Plain `--force` (without `-with-lease`) remains discretionary + needs explicit per-use authorization.
Comment on lines +51 to +52
# 3. Commit + VERIFY HEAD moved
cd /tmp/squash-prep
@AceHack
Copy link
Copy Markdown
Member Author

AceHack commented May 26, 2026

Closing as substrate-stale (DIRTY-conflict) per .claude/rules/pr-triage-tiers.md Tier 3 + the discriminator pass below.

Discriminator pass:

  • Branch prefix: backlog/ or rule/ (past-Otto-CLI session work) → MINE (the maintainer's 2026-05-26 catch: "this is losing to yourself")
  • Substrate state: mergeStateStatus: DIRTY, mergeable: CONFLICTING — branch created 2026-05-25; main has moved ~30 commits since; rebase would need substantial conflict resolution
  • Substrate on main: this PR's B-number is NOT on main today (verified via git ls-tree origin/main -- docs/backlog/) — substrate is genuinely missing, not redundant

Disposition: close. The branch content is preserved in git history; re-land path is cherry-pick onto a fresh branch off current main with any ID-collision renumbering needed. This is the same Tier 3 disposition applied to today's #5038 + #5032 (same root cause: 2026-05-25 evening session left ~9 backlog/rule PRs DIRTY when the next morning's iter-5.x + iter-6 work landed and moved main forward).

This close is NOT a punt — it's explicit ownership classification per .claude/rules/fighting-past-self-vs-peer-agent-distinguisher-fix-your-own-coordinate-on-peers-dont-punt-by-default.md (recurrence anchor landed today via #5126). The substrate-honest re-land path is documented; the operator-tax of indeterminate DIRTY state is cleared.

@AceHack AceHack closed this May 26, 2026
auto-merge was automatically disabled May 26, 2026 08:07

Pull request was closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants