Skip to main content

git-local-hazards

Local git traps where a green command is wrong. Use when branching after a squash-merge, cutting a PR branch, after a clean merge or rebase, when a staged file vanishes, or before reset --hard.

Informações da origem

Repositório
laurigates/claude-plugins
Última atividade na origem
4 de novembro de 2026 às 15:20
Idioma detectado do SKILL.md
inglês
Estrelas
59
Forks
6

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
2 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
git-local-hazards
description
Local git traps where a green command is wrong. Use when branching after a squash-merge, cutting a PR branch, after a clean merge or rebase, when a staged file vanishes, or before reset --hard.
allowed-tools
Read, Grep, Glob, Bash(git *), Bash(gh pr list *), Bash(grep *), TodoWrite
created
2026-09-24T00:00:00.000Z
modified
2026-11-04T00:00:00.000Z
reviewed
2026-09-24T00:00:00.000Z
# Git Local Hazards — Verify the Content, Not the Exit Code Promoted from the always-loaded `~/.claude/rules/git-hazards.md` rule, whose stub keeps the gate lines. The body below is that rule's text. Sibling: `git-plugin:git-merge-hazards` (GitHub PR/merge machinery), which follows the same promotion pattern. Six local-git traps, one law: **a green git command is not proof the result is correct** — exit 0 is a claim about mechanics, not content. Each: the trap, the 5-second check, the fix. ## When to Use This Skill | Use this skill when... | Use something else when... | |---|---| | Starting follow-up work after a branch's PR squash-merged | Deciding whether a branch really landed on GitHub → `git-merge-hazards` | | Cutting a new PR branch | Another agent may be moving HEAD in this clone → `git-coworker-check` | | A merge or rebase reported success over sibling work | Resolving an actual conflict → `git-conflicts` | | A staged file vanished or `git status` flaps | Deleting a whole checkout → `git-repo-delete-check` | | About to `reset --hard` / `checkout` / `restore` / `clean`, or recovering after one | | ## 1. Commits added after a squash-merge are orphaned Anything committed to a branch **after** its squash-merge is in neither `main` nor the squash commit. A fresh branch off `origin/main` silently lacks that work; the first symptom is an ImportError far downstream. - **Check** before follow-up work: `git grep <symbol> origin/main -- <path>` — don't trust "the PR merged". The symbol must be **unique to the change**: a grep for `tier == "deliver"` reported a change as landed when the hit came from a pre-existing line that merely contained the same text (2026-08-19). Prefer `git cherry origin/main <branch>` — `+` means not upstream, and it survives SHA rewriting (rebase, cherry-pick), which plain ancestry checks do not. A squash-merge defeats it two ways: a **multi-commit** squash marks every commit `+`, landed or orphaned (no single commit's patch matches the combined squash commit), and even a single-commit squash reads `+` when `main` edited lines inside the diff's context before the merge (the patch-id cherry compares hashes context lines too). After a squash, take the boundary from the PR's frozen head instead: `gh pr list --head <branch> --state all --json state,headRefOid`, then `git log --oneline <headRefOid>..<branch>` lists exactly the commits the merge did not include. The head SHA is the boundary, not commit dates: a commit made before the merge but pushed after it is dated earlier than `mergedAt`. - **Fix**: replay only the orphans: `git rebase --onto origin/main <squash-point> <branch>`, then verify `git log --oneline origin/main..HEAD` shows only the orphaned + new commits. Confirm nothing was lost by comparing **trees**, not diffs: `git rev-parse HEAD^{tree} <old-tip>^{tree}` must match. ### The variant with no symptom at all: the PR merges WHILE you are still on the branch The framing above assumes you come back later and branch afresh. The quieter case is that you **never leave**: the PR is merged mid-session — by CI, by a teammate, by the user in another window — and you keep committing to the same branch. Every `git push` succeeds, `git status` is clean, and nothing says the branch's PR is closed. Field case: [REFERENCE.md](REFERENCE.md#merged-mid-session-2026-08-19). - **The tell is on the remote, not locally.** `gh pr list --head <branch>` returns nothing (and `--state all` shows MERGED) while you still have commits in `origin/main..HEAD`. - **Do not reuse the merged branch for the new PR.** A merged PR cannot take new commits; push the rebased work to a **new** branch name so the fresh PR is not tangled with the closed one. - Cheap habit: before the *second* push to any branch in a long session, run `gh pr list --head "$(git branch --show-current)" --json number,state`. ## 2. Unpushed commits on local `main` ride into new branches Branching off local `main` inherits whatever it is ahead of `origin/main` by; the PR then bundles stray commits under an unrelated title (squash hides it — visible only in the file list). - **Rule**: cut PR branches from the remote, always: `git fetch origin && git switch -c <branch> origin/main`. - **Check** when unsure: `git log --oneline origin/main..main` — empty means clean. ## 3. A clean textual merge can duplicate identical additions When two branches each add the **same** helper/import/enum arm in non-adjacent spots, `git merge` sees no overlapping hunk, reports success, and keeps **both copies** — a duplicate-definition build break (or worse, silent shadowing in lax languages). - **Check**: build/test the merged tree before committing the merge; when siblings solved related problems, `grep -c '<symbol>' <file>` — expect 1. - **Fix**: hand-resolve to a single combined definition; never trust "Automatic merge went well" as a verdict on content. **The rebase variant: zero conflicts, broken result, no textual overlap.** Same law, and easier to miss because a rebase that replays cleanly *feels* verified. When `main` gains a file that depends on state your branch **removed**, the two never touch the same lines, so there is nothing to conflict on — and the merged tree is still broken. Field case: [REFERENCE.md](REFERENCE.md#clean-rebase-broken-migrations-2026-08). - **Check after any rebase that spans many upstream commits**: list what `main` added while you were away (`git log --oneline --name-only <old-base>..origin/main`) and ask whether any of it depends on something your branch deletes — an extension, a column, a helper, a config key. Then run the suite *on the target environment*, not just the one you develop in. - The durable fix is a **CI matrix over both environments**; a hand-run recorded in a PR description is what let the original divergence through. ## 4. `git add` aborts atomically on a bad pathspec `git add fileA nonexistent` stages **nothing** — not "fileA plus a warning". Classic trip: `git mv old new`, edit `new`, then `git add new old` → the stale `old` aborts the add, and the commit ships the rename with pre-edit content. - **Rules**: run `git status --short` first, then stage every confirmed-present path in ONE `git add -- A B C`, and run `git commit` as a separate call. List every path in that single call rather than chaining `git add A && git add B` — the chain races on `index.lock` and the git-chain hook blocks it (#3042). After `git mv` + edit, stage `<newname>`, not the stale `<oldname>`. - **Check**: `git status --short` before committing — the index column must show the change you intend. - **Recovery**: the edit is still unstaged in the working tree; add and commit/amend — don't redo the work. ## 5. A "vanished" staged file in a shared checkout was probably committed by a coworker Sibling of the push-by-SHA HEAD race in `git-plugin:git-merge-hazards` (the stacked-chain protocol and its three async races): the **index and HEAD are process-global**, so a coworker session's commit lands between two of your Bash calls with no warning. Field case: [REFERENCE.md](REFERENCE.md#vanished-staged-file-2026-07). - **Check first, before any recovery**: `git log --oneline -3` — did HEAD move? — and `git log -1 -- <path>`; a fresh commit touching the path is the discriminator between "lost" and "landed". - **Don't blind-restore.** Re-adding your own copy of a "lost" file can silently **downgrade** the coworker's version (observed: the restored copy lacked frontmatter the coworker had added before committing). Diff your candidate against `HEAD:<path>` and keep the committed version unless yours is genuinely newer. - **Status flapping between consecutive calls is itself the tell** that a coworker is active — stop mutating shared state (index, HEAD, branch switches) until the flapping stops; re-read state fresh in the same command that acts on it (same instinct as push-by-SHA). ## 6. Work destroyed by `reset --hard` is often still in a pre-commit stash Unstaged content has no reflog entry, so this looks unrecoverable — but `pre-commit` stashes unstaged changes around every run, and those stash **commits** go dangling and survive until `gc`. - **Prevent**: `git status` before `reset --hard`/`checkout`/`restore`/`clean`; `git stash -u` anything present. - **Recover**: `git fsck --lost-found`, then per dangling *commit* (not blob) `git show <c>:<path>`; rank by `git diff --numstat HEAD <c> -- <path>` against the diff shape you remember. ## Related - `git-plugin:git-merge-hazards` — GitHub-side traps: squash-merge detection, stacked-PR auto-close, push-by-SHA races, merging over red - `git-plugin:git-coworker-check` — detection before destructive ops, and the shared-checkout branch-isolation check before `git push -u` - `git-plugin:git-pr-sync-check` — is the current PR branch still live
Ver no GitHub