fix(guard): CRAFT_GUARD_ALLOW_DEV_EDIT escape hatch for /craft:git:unprotect deadlock - #305
Merged
Merged
Conversation
…protect deadlock Closes #281. /craft:git:unprotect collects human consent via AskUserQuestion, then writes .claude/allow-dev-edit — but creating that marker is itself intercepted by branch-guard's own confirm gate (edit_guard_bypass / write_guard_bypass / bash_guard_bypass, 3 call sites). In a non-interactive/ auto-mode session there is no way for the already-given consent to resolve the resulting exit-2 block: hooks are stateless per-invocation, and AskUserQuestion's answer isn't visible to the next hook call. A "nonce-bound consent file" approach was adversarially reviewed and rejected in the issue's own investigation: the consent file would just be an ordinary file Claude can write itself, with no AskUserQuestion ever having fired — strictly worse than the status quo. This fix reuses the exact shape already shipped for the structurally identical deadlock on `git branch -D` (issue #168, CRAFT_GUARD_ALLOW_FORCE_DELETE): an env var the user pre-authorizes out-of-band (shell profile / Claude env), which the agent's own Bash tool calls cannot inject into the hook's separate process invocation — so it isn't self-authorizable the way the rejected consent-file approach was. Scoped narrowly to the 3 allow-once/allow-dev-edit marker-write call sites only; does not touch block-all (main), the catastrophic-ops hard blocks, or ~/.claude/settings.json hard_deny, per the issue's own constraints. Verified by reproducing the original deadlock before/after (scratch repo, matches the issue's test requirement): 7 new tests in test_branch_guard.sh cover baseline (still confirms without the env var — regression guard), the new allow-with-env behavior across all 3 call sites, and that the env var does NOT globalize to unrelated MEDIUM-risk gates. 141/141 branch-guard tests, full pytest suite 2615/0 failed. Documented in skills/dev/git/SKILL.md Operation 10 so a future session hitting this deadlock finds the fix instead of looping. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Data-Wise
added a commit
that referenced
this pull request
Jul 25, 2026
reference-scope guard (PR #306)
Data-Wise
added a commit
that referenced
this pull request
Jul 26, 2026
docs/index.md's "Latest" callout and docs/NEWS.md were both still describing v4.3.0 (#305/#306) under a v4.4.0 label — NEWS.md never got a v4.4.0 entry after that release, and index.md's callout inherited the stale text. Also adds [Unreleased] entries to both CHANGELOGs for the two post-4.4.0 fixes from this session: the branch-guard.sh 2>&1 repo-sync (0d11236) and the --refine docs drift (cf11471).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #281.
/craft:git:unprotectcollects human consent viaAskUserQuestion, then writes.claude/allow-dev-edit— but creating that marker is itself intercepted by branch-guard's own confirm gate (edit_guard_bypass/write_guard_bypass/bash_guard_bypass, 3 call sites). In a non-interactive/auto-mode session there is no way for the already-given consent to resolve the resulting exit-2 block: hooks are stateless per-invocation, andAskUserQuestion's answer isn't visible to the next hook call.Adversarial review of the issue's own recommended path: the issue proposes empirically verifying whether
permissionDecision: "ask"denies safely in a genuinely headless session — flagged as a factual unknown requiring verification before implementation. That verification isn't something I can do reliably from this environment, so rather than guess at undocumented hook-protocol behavior, I recognized the deadlock is structurally identical to issue #168 (git branch -Dforce-delete, already fixed this session) and reused that exact escape-hatch shape instead.A "nonce-bound consent file" approach was already adversarially reviewed and rejected in the issue's own investigation: the consent file would just be an ordinary file Claude can write itself, with no
AskUserQuestionever having fired — strictly worse than the status quo.CRAFT_GUARD_ALLOW_DEV_EDITavoids that flaw the same wayCRAFT_GUARD_ALLOW_FORCE_DELETEdoes: it's an env var the user pre-authorizes out-of-band (shell profile / Claude env), which the agent's own Bash tool calls cannot inject into the hook's separate process invocation — not self-authorizable.Scope: narrowly limited to the 3
allow-once/allow-dev-editmarker-write call sites. Does not touchblock-all(main), the catastrophic-ops hard blocks, or~/.claude/settings.jsonhard_deny— all confirmed out of scope per the issue's own constraints.Test plan
test_branch_guard.sh: baseline still confirms without the env var (regression guard), new allow-with-env behavior across all 3 call sites (Edit/Write/Bash-touch), and the env var does NOT globalize to unrelated MEDIUM-risk gatestests/test_branch_guard.shagainst both the repo copy and the installed hook: 141/141 passedpython3 -m pytest tests/— 2615 passed / 0 failed / 49 skipped / 1 xfailed / 1 xpassedskills/dev/git/SKILL.mdOperation 10 so a future session hitting this deadlock finds the fix instead of loopingCo-Authored-By: Claude Sonnet 5 noreply@anthropic.com