From 35047fb3db2a3bd306222c49c0db57f550c484e2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 11 Aug 2026 20:08:53 +0000 Subject: [PATCH] docs(agents): require commit-then-revert for reverse verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `CLAUDE.md`'s "Never `git stash`" block offered four replacements, and the first one — `git checkout origin/main -- ` / `git checkout -- ` — silently destroys uncommitted work in exactly the situation it reads as designed for. `git checkout -- ` copies the file *as committed*; it knows nothing about working-tree edits. During reverse verification the fix is typically not committed yet, so the "restore" half restores the branch's older version and the edit is gone — exit 0, no warning, no conflict, no "would be overwritten" refusal. Already realized, not theoretical: #7739's dev agent (PR #7791) lost an in-flight edit to this recipe and recovered it only by retyping from context. Adopt the correction `objectui` merged this afternoon (c880799, "docs(agents): require commit-then-revert for reverse verification", objectui#4301/#4339) so the two repos' agent docs read the same: - `CLAUDE.md`: drop the hazardous line. The block's purpose is a reflex that is safe *without thinking about it*, and a recipe whose safety depends on an unstated precondition does not serve that. The remaining three forms capture the uncommitted state first and cover the same use case. This leaves the block identical in shape to objectui's copy. - `AGENTS.md` (source of truth) carried the same recipe in its own stash block: removed there too, and the commit-then-revert requirement objectui added is stated in the adjacent prose. Also replaces the weak-verification reach the issue flagged alongside: a matching `git diff --stat` insertion count is not byte-identity, so a retyped recovery is proved with `git diff` against a saved patch or `git hash-object`. Docs-only; no changeset. Fixes #7800 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_011r5id7DkZrVUTZdL3guCC8 --- AGENTS.md | 16 +++++++++++++++- CLAUDE.md | 1 - 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 390cdd5f29..d2ce70e1b1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -191,12 +191,26 @@ the diagnostics") is the workflow every dev agent runs, which is exactly why the window is wide. Use one of these instead — no shared state, all inside your own worktree: ``` -git checkout origin/main -- # then: git checkout -- git diff > /tmp/wip.patch && git checkout -- # then: git apply /tmp/wip.patch git commit -am wip # then: git reset --soft HEAD~1 git worktree add ../objectstack--cmp # a second tree to compare against ``` +**Doing reverse verification? Commit the fix FIRST.** Once it is committed, restoring is +`git checkout -- ` — pulling the file back out of a commit that really +exists. Running the same deletion against an **uncommitted** edit +(`git checkout origin/main -- `) leaves you no restore point at all: the working tree +is the only copy, `git stash` is banned by the rule above, and `git checkout -- ` +discarding local modifications is a normal, silent, exit-0 operation — no warning, no +conflict, no "would be overwritten" refusal. The change is simply gone. Landed twice in one +day in `objectui` (#4278 / PR #4293, #4243 / PR #4299) and once here (#7739 / PR #7791); +every recovery depended on the change still being in the session transcript, so an +incomplete transcript is a net loss. If you ever do retype a lost change, prove it is +identical with `git diff` against a saved patch or `git hash-object ` — a matching +`git diff --stat` insertion count is **not** byte-identity, and this failure mode is silent +enough that only the strong check is worth anything. Then re-run the reverse verification +from the committed state, so the red/green numbers you report are trustworthy. + A third PreToolUse hook (`.claude/hooks/guard-shared-stash.sh`, mirrored from objectui after that incident — #5742) enforces this on the `Bash` matcher: it blocks the mutating forms (`push`/`pop`/`save`/`drop`/`clear`/`branch`, including `stash@{N}` positions, which diff --git a/CLAUDE.md b/CLAUDE.md index 9e58cc4298..01d31288b0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,7 +58,6 @@ in-flight changes, recoverable only as unreachable commits. Use one of these instead — no shared state, all inside your own worktree: ``` -git checkout origin/main -- # then: git checkout -- git diff > /tmp/wip.patch && git checkout -- # then: git apply /tmp/wip.patch git commit -am wip # then: git reset --soft HEAD~1 git worktree add ../objectstack--cmp # a second tree to compare against