#!/usr/bin/env bash # Validate Go files after Edit/Write/MultiEdit. # PostToolUse hook: receives tool-call JSON on stdin. # # On success: exit 0 silent. # On failure: emit JSON with hookSpecificOutput.additionalContext so Claude # sees a structured error in its context, THEN exit 2 to block the tool # pipeline. stderr still echoes the error for terminal visibility. # # Phase 1: gofmt — formatting must be clean. # Phase 2: go vet — compile + suspicious-construct check on the file's package, # run only when the file sits inside a module (go.mod found). go vet # type-checks, so it surfaces compile and syntax errors too. # # Tests deliberately stop here: `go test` on the edited package can pull in slow # or integration-tagged tests that shouldn't fire on every keystroke. Run them # explicitly via `make coverage` / `go test`. set -u # Emit a JSON failure payload and exit 2. Arguments: # $1 — short failure type (e.g. "GO VET FAILED") # $2 — file path # $3 — tool output (error body) fail_json() { local ctx ctx="$(printf '%s: %s\n\n%s\n\nFix before proceeding.' "$1" "$2" "$3" \ | jq -Rs .)" cat <&2 exit 2 } f="$(jq -r '.tool_input.file_path // .tool_response.filePath // empty')" [ -z "$f" ] && exit 0 [ "${f##*.}" = "go" ] || exit 0 [ -f "$f" ] || exit 0 # No toolchain on this machine — nothing to validate, don't block the edit. command -v gofmt >/dev/null 2>&1 || exit 0 # --- Phase 1: formatting --- # gofmt -l prints the path when the file isn't gofmt-clean (and stays silent on # a parse error — go vet catches those in Phase 2). Show the diff so the fix is # obvious. if [ -n "$(gofmt -l "$f" 2>/dev/null)" ]; then fail_json "GOFMT: file is not gofmt-clean" "$f" "$(gofmt -d "$f" 2>&1)" fi # --- Phase 2: vet (needs module context) --- command -v go >/dev/null 2>&1 || exit 0 dir="$(dirname "$f")" gomod="$(cd "$dir" && go env GOMOD 2>/dev/null)" if [ -n "$gomod" ] && [ "$gomod" != "/dev/null" ]; then if ! out="$(cd "$dir" && go vet . 2>&1)"; then fail_json "GO VET FAILED" "$f" "$out" fi fi exit 0