Skip to content

Commit 7abe699

Browse files
JacobPEvansclaude
andauthored
fix: prevent main-branch-guard from blocking non-repository files (#36)
Replace Python implementation with shell script that correctly handles files outside git repositories. ROOT CAUSE ---------- The Python implementation ran branch checks from PWD without validating whether the target file was actually in a git repository. This caused legitimate operations on non-repo files (like ~/.claude/plans/) to be blocked when the current directory was on the main branch. SOLUTION -------- Replaced 110-line Python script with ~60-line shell script that: 1. Extracts file path from JSON input (handles file_path and notebook_path) 2. Checks if file is in a git repository from file's directory context 3. Only applies branch checks if file is actually in a repository 4. Blocks edits to tracked files on main branch Key fix: # NEW: Check git repo from file's directory, not PWD if ! (cd "$file_dir" && git rev-parse --git-dir >/dev/null 2>&1); then exit 0 # Not in repo, allow operation fi TEST RESULTS ------------ All manual tests pass: - ✅ Allows edits to non-repo files (even when CWD is main) - ✅ Blocks tracked files in main worktree - ✅ Allows all edits in feature branches - ✅ Allows untracked files everywhere FILES CHANGED ------------- - git-guards/scripts/main-branch-guard.sh (NEW) - Shell implementation - git-guards/scripts/main-branch-guard.py (REMOVED) - Python implementation - git-guards/hooks/hooks.json - Updated script reference (.py → .sh) - git-guards/README.md (NEW) - Plugin documentation - content-guards/README.md - Simplified for AI-only repo - AGENTS.md - Added hook language selection guidance BENEFITS -------- 1. Fixes plan mode and other non-repo file operations 2. 45% code reduction (110 → 60 lines) 3. Simpler implementation (shell vs Python subprocess handling) 4. Same functionality preserved 5. Better guidance for future hook development Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent b7962f1 commit 7abe699

6 files changed

Lines changed: 129 additions & 269 deletions

File tree

AGENTS.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,12 @@ plugin-name/
6262
**Forbidden fields**: `bugs` (unrecognized by Claude Code runtime)
6363

6464
**Common mistakes**:
65+
6566
- `author` as string instead of object
6667
- `skills`/`commands`/`agents` as arrays of objects with name/description instead of string paths
6768

6869
**Example**:
70+
6971
```json
7072
{
7173
"name": "plugin-name",
@@ -114,6 +116,40 @@ plugin-name/
114116
- Provide actionable error messages
115117
- Follow `noun-verb` naming pattern
116118

119+
### Hook Implementation Language Selection
120+
121+
Choose the implementation language based on complexity:
122+
123+
**Use Shell (bash/zsh) for**:
124+
125+
- Simple git command checks (10-30 lines)
126+
- File existence/path validation
127+
- Basic string matching and JSON field extraction
128+
- Exit code based logic
129+
- Repository-aware file checks
130+
131+
**Use Python for**:
132+
133+
- Complex JSON manipulation beyond simple field extraction
134+
- Multi-step conditional logic (>50 lines)
135+
- Cross-platform compatibility requirements
136+
- Integration with Python-specific tools
137+
138+
**Dependencies**:
139+
140+
- Shell scripts may use: `jq`, `git`, standard Unix tools
141+
- Prefer fewer dependencies when possible
142+
- Always fail-open when dependencies unavailable
143+
144+
**Examples**:
145+
146+
- ✅ Shell: main-branch-guard (~60 lines, extracts JSON field, runs git commands from file's directory)
147+
- ✅ Python: git-permission-guard (command pattern matching and blocking)
148+
- ✅ Python: Complex AST parsing or multi-stage file transformation
149+
- ❌ Python: Simple branch checks (overkill, harder to maintain)
150+
151+
**Key principle**: Simplicity beats sophistication. Use the simplest tool that solves the problem correctly.
152+
117153
## CI/CD
118154

119155
### Workflows

content-guards/README.md

Lines changed: 15 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -1,158 +1,13 @@
1-
# Content Guards
1+
# content-guards
22

3-
Combined validation and guard plugin that enforces content quality and safety rules in Claude Code.
3+
Content validation and guard hooks via PostToolUse.
44

5-
## What This Plugin Does
5+
## Features
66

7-
This plugin merges four validation hooks into a single package:
8-
9-
1. **Token Validator** - Blocks file writes that exceed token limits
10-
2. **Markdown Validator** - Validates markdown with markdownlint and cspell
11-
3. **WebFetch Guard** - Prevents outdated year references in web queries
12-
4. **Issue Limiter** - Prevents GitHub issue backlog overflow
13-
14-
## Token Validator
15-
16-
### How It Works
17-
18-
Intercepts `Write` and `Edit` tools and counts tokens using the `atc` CLI tool.
19-
If the file would exceed the configured limit, the operation is blocked with detailed resolution guidance.
20-
21-
### Configuration
22-
23-
Create a `.token-limits.yaml` file at your repository root:
24-
25-
```yaml
26-
defaults:
27-
max_tokens: 2000
28-
29-
limits:
30-
# Pattern-based overrides (glob matching)
31-
"*.md": 3000
32-
"docs/**/*.md": 4000
33-
"CLAUDE.md": 6000
34-
```
35-
36-
The hook searches upward from the current directory (like git does with `.git/`) to find the config file.
37-
38-
### Resolving Token Limit Violations
39-
40-
When you hit the hard limit, you'll see this error with resolution steps:
41-
42-
```text
43-
❌ Token limit violation: <file>
44-
Tokens: <count> (limit: <limit>, excess: +<over>)
45-
46-
HOW TO RESOLVE — follow these steps in order:
47-
48-
1. REFACTOR INTO MULTIPLE FILES (most common fix)
49-
- Split the file by logical concern (one responsibility per file)
50-
- Use imports/includes to compose the pieces back together
51-
- Example: large Nix module → split into options.nix, config.nix, services.nix
52-
53-
2. EXTRACT EMBEDDED CODE TO SEPARATE FILES
54-
- NEVER embed shell scripts inside Nix files — use a .sh file and reference it
55-
- NEVER embed Python scripts inside YAML (GitHub Actions, etc.) — use a .py file
56-
- NEVER inline large configs — put them in their own file with correct extension
57-
- Each file should contain ONE language/format only
58-
59-
3. REEVALUATE THE DIRECTORY STRUCTURE
60-
- If a file is large because it handles many concerns, rethink the structure
61-
- Create subdirectories to group related smaller files
62-
- Example: monolithic default.nix → directory with focused modules
63-
64-
4. REVIEW FOR DEAD/DUPLICATE CODE
65-
- Remove unused imports, dead code, and duplicated logic
66-
- But NEVER remove comments — comments are always valuable
67-
68-
IMPORTANT — DO NOT:
69-
✗ Remove or reduce comments to save tokens (comments are ALWAYS worth keeping)
70-
✗ Compress code onto fewer lines to fit the limit
71-
✗ Increase the token limit in .token-limits.yaml to paper over the issue
72-
✗ Remove documentation strings or docstrings
73-
74-
The goal is SMALLER, FOCUSED FILES — not less-documented code.
75-
```
76-
77-
**Key principle**: Comments are always valuable. Never remove them to reduce file size. Refactor instead.
78-
79-
### Behavior
80-
81-
- **Success** (under limit): Silent pass (exit 0)
82-
- **Failure** (exceeds limit): Block with detailed error (exit 2)
83-
- **Unavailable** (`atc` not found): Fail open, allow operation (exit 0)
84-
85-
Binary files (`.png`, `.jpg`, `.pdf`, `.bin`, `.zip`) are automatically skipped.
86-
87-
## Markdown Validator
88-
89-
Validates markdown files using `markdownlint-cli2` and `cspell` on `Write` and `Edit` operations.
90-
91-
### Markdown Validator - How It Works
92-
93-
- Runs only on files that appear to be Markdown (typically `*.md`, `*.markdown`)
94-
- Invokes `markdownlint-cli2` with the repository's markdownlint configuration (if present)
95-
- Invokes `cspell` to spell‑check prose and code blocks using the repo's cspell configuration (if present)
96-
- Both tools run against the *post‑edit* content so you see issues from the current change
97-
98-
The exact rule set and dictionaries are controlled by your local markdownlint/cspell config files;
99-
the hook does not alter those rules, it only enforces them on every write/edit.
100-
101-
### Markdown Validator - Behavior
102-
103-
- **Success** (no lint/spell errors): Silent pass (exit 0)
104-
- **Failure** (lint/spell errors found): Operation blocked, tool output shown (non‑zero exit)
105-
- **Unavailable** (tools not found): Fail open, allow operation to proceed (exit 0)
106-
107-
Binary and non‑markdown files are automatically skipped by this validator.
108-
109-
## WebFetch Guard
110-
111-
Blocks `WebFetch` and `WebSearch` operations that contain outdated year references
112-
(e.g., requesting "2024" data when the current year is 2026).
113-
114-
### WebFetch Guard - How It Works
115-
116-
- Intercepts `WebFetch` and `WebSearch` tools before the request is sent
117-
- Scans the query text/URL for explicit Gregorian years (e.g., `2023`, `2024`, `2025`)
118-
- Compares any detected years against the current calendar year
119-
- If the query targets a year older than the current year, treats it as a stale‑data request
120-
- A hard‑coded grace period around New Year allows queries for the just‑previous year
121-
(e.g., early in January) to reduce false positives
122-
123-
The exact grace‑period duration and comparison rules are currently hard‑coded in the hook
124-
implementation and are not user‑configurable; refer to the hook source for precise values.
125-
126-
### WebFetch Guard - Behavior
127-
128-
- **Allowed**: Queries without explicit year literals, or that target the current year
129-
- **Blocked**: Queries with only outdated years outside the grace period; guard explains
130-
why blocked and suggests updating or generalizing the query
131-
- **Unavailable** (tooling/environment not available): Fails open, allows request (exit 0)
132-
133-
## Issue Limiter
134-
135-
Prevents creating GitHub issues when the backlog exceeds a hard‑coded threshold
136-
(to avoid unbounded issue growth).
137-
138-
### Issue Limiter - How It Works
139-
140-
- Intercepts commands that create new GitHub issues (typically `gh issue create`)
141-
- Uses the `gh` CLI to query the current backlog of open issues for the target repository
142-
- Compares the number of open issues against a backlog limit
143-
- The backlog limit is currently a fixed, hard‑coded value inside the hook
144-
(not configurable via settings file yet); consult the hook implementation for the exact threshold
145-
146-
Behavior is deterministic across runs: once the open‑issue count reaches or exceeds
147-
the built‑in threshold, additional issue‑creation attempts through the guarded path
148-
will be blocked until the backlog is reduced.
149-
150-
### Issue Limiter - Behavior
151-
152-
- **Below threshold**: New issues allowed to be created normally (exit 0)
153-
- **At/above threshold**: Issue creation blocked with message indicating backlog limit reached
154-
and suggesting triage/cleanup before adding more issues (non‑zero exit)
155-
- **Unavailable** (`gh` not found, or unable to query issues): Fails open, allows creation (exit 0)
7+
- **markdown-validator**: Validates markdown with markdownlint and cspell
8+
- **token-validator**: Enforces configurable file token limits
9+
- **webfetch-guard**: Blocks outdated year references in web queries
10+
- **issue-limiter**: Prevents GitHub issue backlog overflow
15611

15712
## Installation
15813

@@ -162,11 +17,12 @@ claude plugins add jacobpevans-cc-plugins/content-guards
16217

16318
## Dependencies
16419

165-
- `jq` - JSON processing (used by validation hooks)
166-
- `atc` - Token counting tool (for token-validator)
167-
- `markdownlint-cli2` - Markdown linting (for markdown-validator)
168-
- `cspell` - Spell checking (for markdown-validator)
169-
- `gh` - GitHub CLI (for issue-limiter)
20+
- `jq` - JSON processing
21+
- `atc` - Token counting tool
22+
- `markdownlint-cli2` - Markdown linting
23+
- `cspell` - Spell checking
24+
- `gh` - GitHub CLI
25+
26+
## License
17027

171-
Hooks rely on these external tools; if a dependency is unavailable, the affected hook
172-
may be skipped or may surface an error, and behavior is not guaranteed to fail open.
28+
Apache-2.0

git-guards/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# git-guards
2+
3+
Git security and workflow protection via PreToolUse hooks.
4+
5+
## Features
6+
7+
- **git-permission-guard**: Blocks dangerous git/gh commands (force push, hard reset, destructive operations)
8+
- **main-branch-guard**: Prevents file edits on main branch (enforces worktree workflow)
9+
10+
## Installation
11+
12+
```bash
13+
claude plugins add jacobpevans-cc-plugins/git-guards
14+
```
15+
16+
## License
17+
18+
Apache-2.0

git-guards/hooks/hooks.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"hooks": [
1717
{
1818
"type": "command",
19-
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/main-branch-guard.py",
19+
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/main-branch-guard.sh",
2020
"timeout": 5
2121
}
2222
]

git-guards/scripts/main-branch-guard.py

Lines changed: 0 additions & 109 deletions
This file was deleted.

0 commit comments

Comments
 (0)