feat(hooks): lift the nix run/shell and git push confirmation gates - #2733
Merged
Conversation
Both arms of gate-dangerous-commands returned permissionDecision "ask", which is a hard stall for an agent worker launched with permissions bypassed: the confirmation never reaches a human until somebody reads the pane. Four such stalls were observed across two workers on 2026-08-16 and 2026-08-17, and neither `nix run` nor `git push` is avoidable in normal work in this repository. git push now auto-permits in every form with an ntfy NOTICE, following the relaxation convention this file already established for non-default-ref pushes (2a2e598), canonical `gh pr create -d` (d602b2c), and .zt ssh/scp/rsync (d8e2532). This subsumes push_is_safe, so that helper is removed; the surviving escalating cases it distinguished -- force push, delete refspec, push to the default ref -- now notify instead of asking. nix run/shell is removed outright rather than converted to a NOTICE. It is a single arm carrying one message, "nix run/shell executes arbitrary code", so lifting the named gate lifts both subcommands; a notification per `nix run` would be pure noise at the rate this repository invokes it. Deliberately left in place, as a separate risk class for a separate decision: raw writes and secure deletion (dd, truncate, shred), sudo, `jj git push`, git reset --hard / clean / checkout . / restore . / branch -D / stash drop, mutating gh api / pr / issue / repo / release / workflow / gist, tofu and terraform apply/destroy, kubectl and helm mutation, non-.zt ssh/scp/rsync, docker and podman push, pattern-matched process termination, and the rm bypass vectors (find -delete, find -exec rm, xargs rm). The companion hooks redirect-rm-to-rip, gate-mutating-http, gate-git-worktree, gate-worktree-surfaces, enforce-branch-before-edit and verify-diamond-before-edit are untouched. Blast radius is confined to Claude Code: gate-dangerous-commands is referenced only by modules/home/ai/claude-code/hooks.nix. Pi's shell policy is a separate engine (modules/home/ai/pi/policy/permission-rules.ts) and gates neither command. No OpenSpec requirement binds these gates, so no spec delta is needed. The settings.json retraction trap does not apply on this path twice over: the gates are script content rather than settings keys, and Claude Code settings are installed wholesale by claude-code/default.nix rather than through atomic's merge-settings.sh, which is the mechanism that genuinely cannot retract a key. Documentation updated in the same change: the hook's own --help category list, the commented-out static ask list in claude-code/default.nix that is explicitly retained as documentation of gated categories, and the two places nix-flake-pr-cycle/SKILL.md describes push gating. Testing: `nix build .#checks.aarch64-darwin.hook-gate-dangerous-commands`, the one oracle that drives this script. Its case table previously covered only the kill arm, so it bound neither removed gate; twenty cases were added -- eight pinning the lifted behavior, twelve pinning neighbouring gates that must not move. Severity was checked by replaying the new cases against the pre-change script: six of the eight lifted-gate cases fail there, and the retained-gate cases hold on both sides. Also evaluated homeConfigurations."crs58@aarch64-darwin".activationPackage to confirm the module edits still evaluate, and built the hook derivation directly, which runs shellcheck via writeShellApplication and confirms removing push_is_safe left no dead function. The full `just check-fast` suite was deliberately not run: nothing outside this script and its own check changed behavior.
cameronraysmith
force-pushed
the
fm/vx-lift-agent-hook-gates
branch
from
August 17, 2026 04:52
a35b828 to
5aadf36
Compare
cameronraysmith
marked this pull request as ready for review
August 17, 2026 04:57
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.
Lifts the two Claude Code hook gates the captain authorized on 2026-08-17:
nix runandgit push. Both returnedpermissionDecision: "ask", which is a hard stall for an agent worker launched with permissions bypassed, and neither command is avoidable in normal work here.Where the gates actually live
Not in
settings.json. Both are escalation arms insidemodules/home/tools/hooks/gate-dangerous-commands.sh, awriteShellApplicationon PATH thatmodules/home/ai/claude-code/hooks.nixnames as aPreToolUse:Bashhook. The settings file only references the hook by name, so its hook wiring is unchanged by this PR.What changed
git pushauto-permits in every form with an ntfy NOTICE, following the relaxation convention this file already established for non-default-ref pushes (2a2e598), canonicalgh pr create -d(d602b2c), and.ztssh/scp/rsync (d8e2532). That subsumespush_is_safe, so the helper is removed; the cases it used to escalate (force push, delete refspec, push to the default ref) now notify instead of asking.nix run/nix shellis removed outright rather than converted to a NOTICE. It is a single arm carrying one message,"nix run/shell executes arbitrary code", so lifting the gate the captain named lifts both subcommands. A notification pernix runwould be pure noise at the rate this repo invokes it.Gates inventoried and deliberately left in place
Not touched, for the captain to decide separately:
dd,truncate,shredddgate that was observed firingsudojj git push--all-bookmarks/forcegit reset --hard,clean,checkout .,restore .,branch -D,stash drop/cleargh api/pr/issue/repo/release/workflow/gisttofu/terraform apply/destroy,kubectl apply/create/delete/exec,helm install/upgrade/uninstall.ztssh/scp/rsyncdocker/podman pushpkill/killall,killwith a non-literal PID,xargs killfind -delete,find -exec rm,xargs rmrmbypass vectorsCompanion hooks are untouched:
redirect-rm-to-rip(deniesrm),gate-mutating-http(asks on mutating curl/wget),gate-git-worktreeandgate-worktree-surfaces(ask before making a plain-git worktree),enforce-branch-before-edit(denies edits on the default branch),verify-diamond-before-edit(asks on jj diamond-integrity violations).Obligations checked
modules/checks/hooks.nixis the only oracle driving this script, and its case table covered only thekillarm, so nothing asserted the removed shape. Twenty cases added: eight pinning the lifted behavior, twelve pinning neighbouring gates that must not move.openspec/specs/pi-agent-environment/spec.md:117, governs Pi's separate TypeScript policy engine (modules/home/ai/pi/policy/permission-rules.ts), which gates neither command. No spec delta needed.--helpcategory list, the commented-out static ask list inclaude-code/default.nixthat is explicitly retained as documentation of gated categories, and the two placesnix-flake-pr-cycle/SKILL.mddescribes push gating.install -Dm644,claude-code/default.nix:414-430) rather than throughmodules/home/ai/atomic/merge-settings.sh, which is the mechanism that genuinely cannot retract a key and which only ever touches~/.atomic/agent/settings.json.Blast radius
Confined to Claude Code.
gate-dangerous-commandsis referenced only bymodules/home/ai/claude-code/hooks.nix; no other agent consumes it. The firstmate repository's own hooks were not read or modified.Verification
Ran
nix build .#checks.aarch64-darwin.hook-gate-dangerous-commands— the one oracle covering this script — which passes with all 43 cases.Severity was checked rather than assumed, by replaying the new cases against the pre-change script: six of the eight lifted-gate cases fail there (
nix run,nix shell, baregit push,git -C /p push,git push --force origin main), whilegit push -u origin fm/...andnix buildwere already permitted and serve as anchors rather than regression detectors. The retained-gate cases hold on both sides.Behavior was then confirmed against the built derivation, not the source and not this session, since a running session cannot pick up the change without
just activate:Also evaluated
homeConfigurations."crs58@aarch64-darwin".activationPackageto confirm the module edits still evaluate, and built the hook derivation directly, which runs shellcheck viawriteShellApplicationand so confirms removingpush_is_safeleft no dead function.The full
just check-fastsuite was deliberately not run. Nothing outside this script and its own check changed behavior, and a whole-fleet check run is far wider than the diff.Incidental finding — not fixed
The gates pattern-match the whole Bash command string, so a gated word in a quoted string or heredoc trips them even though no such command runs. The first commit's own message contains
find -deletein prose describing the gates left in place; replaying that commit command against the deployed hook returnsaskwith reasonfind -delete removes files. A heredoc body does not escape this because it is still part of the command — passing the message through a file withgit commit -F <file>does, which is how the second commit was made.The three
rm-bypass arms are the worst affected because theygrepthe raw command with no start-of-line or shell-operator anchoring at all, unlike thecmd_matchhelper the other arms use. Narrowing them is a change to gates this task was not authorized to touch, so they are reported rather than altered.