Skip to content

fix(guard): CRAFT_GUARD_ALLOW_DEV_EDIT escape hatch for /craft:git:unprotect deadlock - #305

Merged
Data-Wise merged 1 commit into
devfrom
feature/issue-281-unprotect-deadlock
Jul 25, 2026
Merged

fix(guard): CRAFT_GUARD_ALLOW_DEV_EDIT escape hatch for /craft:git:unprotect deadlock#305
Data-Wise merged 1 commit into
devfrom
feature/issue-281-unprotect-deadlock

Conversation

@Data-Wise

Copy link
Copy Markdown
Owner

Summary

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.

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 -D force-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 AskUserQuestion ever having fired — strictly worse than the status quo. CRAFT_GUARD_ALLOW_DEV_EDIT avoids that flaw the same way CRAFT_GUARD_ALLOW_FORCE_DELETE does: 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-edit marker-write call sites. Does not touch block-all (main), the catastrophic-ops hard blocks, or ~/.claude/settings.json hard_deny — all confirmed out of scope per the issue's own constraints.

Test plan

  • Reproduced the original deadlock before/after (scratch repo), per the issue's own test requirement
  • 7 new tests in 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 gates
  • tests/test_branch_guard.sh against both the repo copy and the installed hook: 141/141 passed
  • Full suite: python3 -m pytest tests/ — 2615 passed / 0 failed / 49 skipped / 1 xfailed / 1 xpassed
  • 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

…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
Data-Wise merged commit b3befc7 into dev Jul 25, 2026
2 checks passed
@Data-Wise
Data-Wise deleted the feature/issue-281-unprotect-deadlock branch July 25, 2026 02:42
Data-Wise added a commit that referenced this pull request Jul 25, 2026
@Data-Wise Data-Wise mentioned this pull request Jul 25, 2026
4 tasks
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).
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.

1 participant