Skip to content

Conversation

@licht1stein
Copy link
Owner

Summary

Add user-configurable stop hooks that run when Claude Code fires the Stop event. Users can define hooks in .brepl/hooks.edn to run checks (linters, tests) before Claude finishes working.

Changes

  • New lib/stop_hooks.clj with spec validation, REPL/bash execution, retry tracking
  • Add brepl hook stop CLI command to handle Stop event
  • Idempotent hook merging in installer (preserves non-brepl hooks)
  • Generate .brepl/hooks.edn template on brepl hook install
  • Fix handle-session-end to parse JSON from stdin (matching Claude Code protocol)

Hook Configuration

{:stop [{:type :bash
         :command "clj-kondo --lint ."
         :required? true    ; Must pass - retries on failure
         :max-retries 10}
         
        {:type :repl
         :code "(clojure.test/run-tests)"
         :required? true}]}

Exit Code Behavior

  • 0 - All hooks passed, Claude can stop
  • 1 - Hook failed (optional or max retries reached), Claude informed
  • 2 - Required hook failed, Claude must continue working

Testing

  • 57 tests (241 assertions), all passing
  • Manual testing of exit codes and retry behavior

Add configurable hooks that run when Claude Code fires the Stop event.
Users configure hooks in `.brepl/hooks.edn` with two types:

- `:repl` - Execute Clojure code via nREPL
- `:bash` - Run shell commands

Hooks support retry-on-failure with configurable max retries. Exit codes:
- 0: success, Claude can stop
- 1: failure reported, Claude can stop
- 2: blocking, Claude must continue working

Also fixes `handle-session-end` to parse JSON from stdin instead of
expecting command-line args, matching Claude Code's hook protocol.
- Replace `:retry-on-failure?` with `:required?` for unified semantics
- Make `:name` optional (auto-derived from command/code)
- Include stdout in error output so Claude sees actual failures
- Add "Fix the issues shown below" guidance in retry messages
- Handle nREPL connection errors with actionable message
- Exit 2 on first no-server attempt, exit 1 on second (forces Claude to react)
Accept both forms:
  :code (restart)           ; s-expression (preferred)
  :code "(restart)"         ; string still works
@licht1stein licht1stein merged commit ce8b488 into master Jan 4, 2026
2 checks passed
@licht1stein licht1stein deleted the stop-hooks branch January 4, 2026 19:40
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.

2 participants