feat(hooks): enforcement hooks — PreToolUse git gate + SessionStart brief (3.2.0) - #33
Merged
Conversation
Adds MUTATION_SUBCOMMANDS + is_mutation(seg) to hook_gate, plus corpus regression tests over 680 real command shapes. No parser changes were needed: detection rate is 255/263 (97.0%). The 8 misses are legitimate fail-open skips (6 heredoc commit bodies unparseable by shlex, 2 git strings embedded in quoted echo args piped to another script).
…flag resolution, newline splitting, stash reads - push refspec check: scan positionals properly — stop at redirects (2>&1, > file, <) and background &, skip values of -o/--push-option/ --repo/--receive-pack/--exec, so shell trailers are never treated as refspecs (was a false block on `git push origin main 2>&1`) - resolve_segments: `git -c key=val -C dir` now resolves the -C dir (the -c value token no longer aborts the global-flag scan) - cd branch: skip leading -P/-L/-e/-- option tokens before taking the target; flag-like or missing target marks dir unknown (fail open) - split_top_level: unquoted depth-0 newlines split like `;`, so `cd api\ngit commit` chains are gated (corpus rate held at 255/263) - is_mutation: `git stash list`/`stash show` are reads; only bare stash and push/pop/apply/drop/clear/save/branch sub-verbs gate - trunk-drift deny reason: no longer suggests `canopy switch None` when no feature is canonical - style: re import at module top, dead quote-strip removed from _resolve_path, gate_command docstring corrected, redaction-guard comment added; new test covers per-repo branches map in owner lookup
…ations - split_top_level: unquoted depth-0 <<DELIM (<<EOF, <<-EOF, <<'EOF', <<"EOF"; <<< herestrings ignored) suppresses ALL splitting — newlines and operators — until the line matching DELIM, then resumes. Fixes two round-1 regressions from newline splitting: a heredoc body that merely mentions `git push` was blocked outside_repo, and a body `cd` line poisoned the tracked dir for a real mutation after the heredoc. An unterminated heredoc leaves the remainder as one segment (fails open). - is_mutation: a flag right after `stash` (`stash -u`, `--keep-index`, `--include-untracked`) is an implicit `stash push` — gated as a mutation; `stash list`/`show` stay reads. Corpus: heredoc change loses zero shapes; stash-flag fix detects one more (242/263 tagged; the 13 stash-read shapes were intentionally reclassified by round 1's fix 5, remaining 8 misses pre-date round 1).
…ors, herestring guard - _heredoc_delimiter: accept the POSIX backslash-quoted form <<\EOF (equivalent to <<'EOF') so its body is swallowed instead of newline-split (was a verified false block on a body mentioning `git push`) - terminator matching: plain << now requires the line to equal the delimiter EXACTLY (an indented ` EOF` or trailing-space `EOF ` line no longer ends the heredoc early and leaks body lines as gated segments); <<- strips leading tabs only, per shell semantics - herestring guard: also bail when the char before << is `<`, so the scan cannot re-enter <<< at its middle `<` and open a bogus heredoc that swallowed all subsequent real commands (detection regression, fail-open direction) Corpus rate unchanged at 242/263.
…-check/--hooks status, no-workspace skip, numeric slot sort, shim tests
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.
What
Canopy 4.0 phase 2: Claude Code enforcement hooks that make the two core mistakes impossible rather than merely discouraged, installed by
canopy setup-agent --hooksinto project-scoped.claude/settings.json.Motivated by mining 35 days of real work-machine transcripts (135 git errors / 111 midway branch switches / 82 wrong-path incidents). See
~/.claude/plans/canopy-4.0-distillation.md#evidence.PreToolUse git gate (
canopy-hook-gate)Parses each Bash command into per-segment effective directories (resolving
cdchains,git -C, and heredoc bodies — the agent's cwd never leaves the workspace parent, so raw-cwd checks would false-positive on ~357 legitimatecd x && git …chains). Blocks git mutations that:outside_repotrunk_branch_drift/slot_branch_driftpush_unknown_branch(the realsrc refspec … does not match anyfailure)Fail-open by design: unparseable commands, unresolvable
cd $VAR, no canopy.toml, or any internal error → allow.CANOPY_HOOKS_DISABLED=1disables entirely.git checkout/switchare never blocked (they're the recovery action).SessionStart brief (
canopy-hook-context)Injects a compact
repo → branch (dirty) · canonical feature · slotsmap at session start so a wrong-branch session is visible before the agent reads a file.Correctness
tests/fixtures/hook_gate_corpus.jsonl) — regression test asserts no crashes + ≥90% mutation detection (96.8% on true mutations; the rest are legit fail-open skips like git-inside-subshell).-c/-Cinteraction, cd-flag resolution) + one on the installer (malformed-settings shape-safety, atomic write,--checkreporting).Tests
942 passed. ~110 hook-specific tests across
test_hook_gate,test_hook_gate_corpus,test_hook_context,test_install_hooks,test_hooks_entry.Version
3.2.0 (CHANGELOG updated). Fast-path latency ~6ms import / ~50ms end-to-end; the gate only loads a workspace when
gitappears in the command.Not in this PR (deferred)
Manual multi-chat dogfood in canopy-test → deferred to post-4.0.0 per project decision. Automated replay harness (drives the real binary through mined scenarios) planned before phase 4.