Skip to content

feat: add shared agent hooks and Claude Code permissions to the template - #37

Merged
mjun0812 merged 1 commit into
mainfrom
feat/agent-hooks-and-rules
Sep 7, 2026
Merged

mjun0812 merged 1 commit into
mainfrom
feat/agent-hooks-and-rules

Conversation

@mjun0812

@mjun0812 mjun0812 commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Overview and Background

Generated projects now ship a shared agent hook that formats and lints every Python file right after Claude Code or Codex edits it, a committed Claude Code settings file with pre-approved development commands, and an AGENTS.md that records the project-specific rules established over the last template changes. Previously the template only provided AGENTS.md / CLAUDE.md text: agents had to be prompted for every uv run invocation, formatting only happened at commit time through prek, and rules such as the TYPE_CHECKING import pattern or the coverage threshold lived only in CI failures.

Related Issues

None

Implementation Approach

  • One script, .agents/hooks/format-python.sh, serves both tools because Claude Code and Codex use the same hook event schema (PostToolUse, matcher, command hooks, exit code 2 = feedback to the agent). The script reads the hook JSON from stdin and collects Python paths from tool_input.file_path (Claude Code Edit/Write) or from the *** Add File / *** Update File lines of tool_input.command (Codex apply_patch, which also matches Edit|Write). It runs ruff format and ruff check --fix; diagnostics that cannot be auto-fixed go to stderr with exit code 2 so the agent fixes them itself. Non-Python edits exit 0 immediately. The script changes to the hook's cwd (falling back to the git root) so relative paths resolve wherever the agent was started.
  • .claude/settings.json is the committed, team-wide Claude Code configuration: it wires the hook and pre-approves uv run --frozen, uv sync, uv lock and read-only git commands, while denying Read of .env files. .claude/settings.local.json (personal overrides) is added to the generated .gitignore.
  • .codex/hooks.json wires the same hook for Codex with the documented hooks.json shape.
  • AGENTS.md gains the rules that are otherwise only enforced by tooling: never edit uv.lock by hand, the TYPE_CHECKING import pattern required by Ruff's TC rules, that pytest runs in parallel (-n 0 for --pdb) and CI fails below 80% branch coverage, never git commit --no-verify, the run command, and a note that the agent hook already formats edits.

Changes

  • template/.agents/hooks/format-python.sh: new hook script (shellcheck and shfmt clean).
  • template/.claude/settings.json: permissions and PostToolUse hook.
  • template/.codex/hooks.json: PostToolUse hook.
  • template/.gitignore: ignore .claude/settings.local.json.
  • template/AGENTS.md: rules listed above.
  • README.md: describe the agent configuration files.

Impact

  • User-facing: after an agent edits a .py file, the file is formatted immediately and remaining lint errors are fed back to the agent. Users see fewer permission prompts in Claude Code for the listed commands.
  • Trust: Claude Code applies permissions.allow from a project only after the workspace has been trusted once interactively; hooks run regardless. Codex loads project hooks only for trusted projects.
  • The hook needs jq, uv, and git on PATH; all are present in the dev container and required by the existing workflow.
  • CI, Docker, and packaging are unchanged.

Validation Results

Direct script tests in a generated Python 3.14 project (stdin JSON crafted from the documented shapes):

# Claude Code shape: formatted, unfixable F401 reported, exit 2
echo '{"cwd":"$PWD","tool_input":{"file_path":"src/test_copier/a.py"}}' | bash .agents/hooks/format-python.sh
# Codex apply_patch shape with two files and a deleted README: both formatted, exit 2
jq -n --arg cmd "$(printf '*** Begin Patch\n*** Update File: src/test_copier/b.py\n...\n*** Add File: src/test_copier/c.py\n...\n*** Delete File: README.md\n*** End Patch')" \
  '{tool_name:"apply_patch", tool_input:{command:$cmd}}' | bash .agents/hooks/format-python.sh
# Clean file: exit 0. Non-Python file: exit 0 without running ruff.

End-to-end with Claude Code in the same project:

claude -p --allowedTools "Write,Edit,Read" 'Create src/test_copier/from_claude.py with <badly formatted content> ... fix any hook diagnostics'
# Claude's report: Write triggered the hook, which reformatted the file; the remaining F401 was
# fixed with a second Edit, after which the hook reported no errors.
uv run --frozen ruff check src/test_copier/from_claude.py          # All checks passed!
uv run --frozen ruff format --check src/test_copier/from_claude.py  # 1 file already formatted

Codex end-to-end could not be completed: codex exec loaded the session hooks but the account hit its usage limit before the edit. The Codex path is covered by the apply_patch-shaped direct test above and by the documented tool_input.command field.

@mjun0812 mjun0812 added the enhancement New feature or request label Sep 7, 2026
@mjun0812 mjun0812 self-assigned this Sep 7, 2026
@mjun0812
mjun0812 force-pushed the feat/agent-hooks-and-rules branch from 97f4f43 to f5b0773 Compare September 7, 2026 23:11
- Add .agents/hooks/format-python.sh, a PostToolUse hook that runs ruff
  format and ruff check --fix on the Python file an agent just edited
- Wire the hook for Claude Code (.claude/settings.json) and Codex
  (.codex/hooks.json) using the same event schema
- Pre-approve uv run --frozen, uv sync, uv lock and read-only git
  commands for Claude Code and deny reading .env files
- Ignore .claude/settings.local.json in generated projects
- Record the TYPE_CHECKING import rule, the parallel pytest note, the
  --no-verify ban, the run command and the agent hooks in AGENTS.md
@mjun0812
mjun0812 force-pushed the feat/agent-hooks-and-rules branch from f5b0773 to f2b8190 Compare September 7, 2026 23:12
@mjun0812
mjun0812 merged commit 9a444ab into main Sep 7, 2026
7 checks passed
@mjun0812
mjun0812 deleted the feat/agent-hooks-and-rules branch September 7, 2026 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant