working-tree-edits-stranded-on-squash-merge · v1.0.0 · 2026-05-29 · sha256 ea08cd3640e1db9f
working-tree-edits-stranded-on-squash-merge v1.0.0A
Immutable. This exact content is served forever at /api/v1/blob/ea08cd3640e1db9f.
--- name: working-tree-edits-stranded-on-squash-merge description: | Diagnose and prevent "I made the fix but it's not on main" cases where the fix was applied in the working tree via Edit / Write but never `git add`ed before the squash-merge. Use when: (1) you squash-merged a PR and the user reports the issue is back, (2) `git status` after the merge shows uncommitted changes for files you remember editing during the PR's acceptance pass, (3) a follow-up `git diff origin/<base>` against the merged branch surfaces edits that should have been in the PR. Root cause: the harness's Edit / Write tool modifies the working tree but does NOT stage changes; a subsequent `git add <other_file>` + `git commit` leaves the Edit'd file in the working tree. When the PR merges, the squash captures only what was committed. Sister-skill to `pr-followup-commit-stranded-after-squash` (that one covers commits pushed after the merge; this one covers edits never committed at all). author: Claude Code version: 1.0.0 date: 2026-05-18 --- # Working-tree edits stranded by a squash-merge ## Problem You're iterating on a PR through an acceptance pass. The user surfaces a bug; you make an Edit() call; the bug is fixed locally; the user confirms; you move on to the next bug. A few iterations later you squash-merge the PR. The user reloads the page and says "the bug is back." The fix is sitting in your working tree, never staged, never committed. The squash captured everything *committed* on the branch — but Edit() and Write() don't auto-stage. When you did `git add <other_file>` for the follow-up bug, only that file went into the commit; the earlier Edit'd file stayed in the working tree as a modified-but-uncommitted change. After merge: `git status` shows `M <file_you_edited>`, the merged squash on `<base_branch>` has the old content, and the user sees the bug return. ## Context / Trigger Conditions All of these together strongly indicate this pattern: 1. You used Edit() or Write() on a file during an interactive PR-review pass (i.e. you applied the fix to "make the live server / preview reflect it" for the user to verify, but didn't think of it as a commit-worthy unit). 2. The user later flagged a different bug; you fixed THAT one with `git add <different_file>` + `git commit`. 3. Now the PR is merged but the working tree (when you re-enter the worktree) shows `M <file_from_step_1>`. 4. The squash commit on `<base_branch>` does NOT include the changes from step 1. 5. User-facing symptom: the original bug reappears as if the fix never landed. ## Solution ### Prevent — pre-merge audit Before any `gh pr merge`, run: ```bash git status --short # any `M` or `??` lines? git diff # any uncommitted hunks? git diff origin/<base_branch> # confirm the diff matches your mental model ``` If `git status` shows un-staged modifications, decide for each one: - Should it be part of THIS PR? → `git add <file>` + commit + push (then merge). - Should it stay local? → stash or .gitignore it explicitly so you know it's intentionally not in the PR. - Is it a leftover from a prior session? → triage (rarely common but happens in long-running worktrees). Never run `gh pr merge` while `git status` is non-empty without an explicit reason. The harness will let you, GitHub will let you, and the user will report the bug as back within hours. ### Recover — hotfix PR If you've already merged and discovered the strand: ```bash git fetch origin git checkout -B hotfix/<short-name> origin/<base_branch> # The working-tree edits survived the checkout because they're still # uncommitted. Stage them now: git add <files_that_were_stranded> git diff --staged # sanity-check before commit git commit -m "hotfix(<area>): <fixes that missed the squash> <one paragraph per stranded fix explaining the bug + why it missed>" git push -u origin hotfix/<short-name> gh pr create --base <base_branch> --title "..." --body "..." gh pr merge <new_pr> --squash ``` If the stranded edits accidentally got reverted (e.g. you ran `git checkout HEAD -- <file>`), the working tree is no longer authoritative — recover from the Edit tool's history or re-apply from notes. ## Verification After the hotfix lands, the symptom should resolve. Specifically: - `git diff origin/<base_branch> origin/hotfix/<name>` shows ONLY the stranded edits (no surprise extras). - `git status` on the worktree is clean after the hotfix is checked out / pulled. - The user-facing symptom is reproducible BEFORE and gone AFTER. ## Example a causal-impact engagement, S60 acceptance pass (2026-05-18): During PR #90 acceptance the user reported two distinct UI bugs in sequence: 1. The /runs Plotly chart looked squashed at the bottom because the y-axis range was computed from masked-period BSTS extrapolations (~2.5M) while visible data sat ~200K-300K. **Fix**: Edit'd `webapp/templates/report.html` to apply `isMasked()` in the y-range loop. 2. The /results/<slug> page didn't show the download buttons because the opportunistic DB save in `results_page` threw `ModuleNotFoundError` from `from .data_storage import ...` (wrong relative-import depth). **Fix**: Edit'd `webapp/views/analysis.py` to change `.data_storage` → `..data_storage`. Both fixes worked in the live server (Flask debug reloader picked the files up). User verified the runs page. THEN I committed an unrelated file (`webapp/templates/runs.html` for the two-button toolbar) — but forgot to `git add` the earlier two files. The squash-merge captured the toolbar change but neither fix. After merge: user said "buttons still not there", `git status` in the worktree confirmed the strand: ``` M webapp/templates/report.html M webapp/views/analysis.py ``` Recovery PR [#93](https://github.com/<org>/the-causal-impact-repo/pull/93) shipped both fixes in 8 minutes via the recipe above. The harness warning `Warning: 3 uncommitted changes` on `gh pr create` could have caught this if I'd been paying attention to it during PR #90 too — it fires on merge with the same wording. ## Notes - **The Edit / Write tools are deliberately non-staging.** The user can reset or selectively stage their working tree the way they would for any other dev workflow. This is correct behavior; the workflow gap is yours, not the tool's. - **The `Warning: N uncommitted changes` from `gh pr create` is the canary.** If you see it before a merge command, stop and `git status`. - **`git diff origin/<base>` is the auditable truth.** It shows exactly what the squash will contain. Anything you expect that isn't there is stranded. - **Doesn't apply to scratch / preview-only edits.** Sometimes you Edit a file just to show the user a rendered preview, then discard. If you consciously decide an edit shouldn't ship, that's fine; the trap is edits you THINK shipped but didn't. ## References - Sister skill: `pr-followup-commit-stranded-after-squash` — same outcome (changes missing from main), different cause (commits pushed *after* merge instead of edits never committed). - `git status` semantics: [Pro Git §2.2 — Recording Changes](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository)