Appendix E — Quality Gates and Hooks
Hooks provide useful local feedback and bounded blocking. They are not an unbypassable security boundary or proof that an entire project is correct. This appendix describes the shipped reference adapters, their installation checks and their limitations. Use the executable files in the kit instead of maintaining separate copied scripts from prose.
Hooks are named by purpose, not sequence. Historical anchors and installed filenames remain compatible. Operating model, testing reference.
| Hook name | Maintained script | Behavior |
|---|---|---|
| Post-edit lint check | lint-on-edit.sh | Advisory, read-only lint within its supported file scope |
| Session-end verification reminder | verify-before-stop.sh | Debug-log reminder; does not execute or verify tests |
| Pre-push quality gate | pre-push-gate.sh | Blocks supported push tool calls on configured gate failure |
| Documentation freshness check | check-doc-staleness.sh | Advisory age/metadata check; does not verify factual accuracy |
| Memory budget check | memory-budget-check.sh | Advisory size/entry hygiene check; does not rewrite memory |
The settings template uses these descriptive names as statusMessage values for its four registered handlers. Claude Code documents this field as the spinner message while a hook runs; it is presentation, not a control or execution receipt. Existing project settings are preserved by the installer, so adopt the labels through a reviewed merge. The documentation freshness script still requires an explicit document directory and project-specific registration; this naming change does not activate an additional hook. See hook handler fields.
Table of Contents
- 3-Layer Defense Architecture
- Hook Type Reference
- Post-edit lint check
- Session-end verification reminder
- Pre-push quality gate
- Documentation freshness check
- Memory budget check
- Hook Configuration Reference
- Integration with Superpowers
- Creating Custom Hooks
- Troubleshooting
1. 3-Layer Defense Architecture
Three local hook layers complement a fourth, server-side merge/release layer. Each observes a defined scope; none inherits another layer's passing status.
Text equivalent: local edits can produce diagnostics; the shipped Stop adapter writes a debug-log reminder; a recognized push can be blocked. Model-visible feedback requires a supported, host-tested delivery mechanism. All delivery paths still need effective server controls and a distinct release decision.
Layer Characteristics
| Layer | Observed scope | What it does not establish |
|---|---|---|
| Post-edit lint check | Recognized file/tool events and configured lint scope | Complete-project quality or rollback of the edit |
| Session-end verification reminder | Selected local change indicators | Whether required tests actually executed or passed |
| Pre-push quality gate | Recognized command forms and configured checks | Other actors, all shell syntax, business approval or release |
| Server | Exact required contexts, branch rules and approval policy | Controls it does not require, bypass prevention beyond its configuration, business truth |
Measure latency in the adopter's environment. Previous fixed sub-second/ten-second claims were illustrative, not guarantees.
Why Three Layers
Early feedback reduces avoidable delay. A reminder can help completion discipline. Pre-push checks catch recognized failures before publication. These complementary benefits do not make the layers independent or exhaustive; required server checks and protected acceptance criteria remain necessary.
2. Hook Type Reference
These are the events used by the Claude adapter, not an exhaustive list of events supported by every runtime. Verify the installed runtime's documented contract. The official hooks reference distinguishes blocking exit 2 from other nonzero errors; do not translate a generic shell failure into assumed tool denial.
PreToolUse
Runs before a supported tool call. Validate the actual event payload and tool input; a missing/malformed required field is not a clean assessment. For this adapter, use exit 2 and an actionable, sanitized diagnostic to block. Exit 1 is an execution error, not the blocking convention taught by older examples.
The kit's templates/hooks/pre-push-gate.sh is the maintained implementation. A passing shell invocation alone does not prove that the host invoked it or respected the result—test the installed integration.
PostToolUse
Runs after the supported tool has executed. Feedback cannot undo that action. The supplied lint adapter runs read-only checks on resolved Python paths within the installed project's backend/app or backend/tests. It rejects paths resolving outside those directories, including symlink escapes. Its exit-0 plain output is debug-log-only under the documented Claude contract, not a delivered message to the model. Host-test a supported structured output mechanism before claiming interactive feedback; keep mandatory comprehensive assessment in CI.
Stop
The supplied templates/hooks/verify-before-stop.sh is a diff-aware, debug-log-only command: plain stdout with exit 0 does not enter Claude's context. It does not collect receipts, validate all expected tests, deliver a verified model reminder or force verification. Other runtime hook types may have different capabilities; this claim is about the shipped adapter.
Post-edit lint check
Use immediate, bounded checks selected for the project's stack. Ensure the path under inspection is the actual edited path and lies within the declared project; a failed directory change must not run checks in another repository.
settings.json Configuration
Start from templates/settings.json and the hook paths installed by setup/bootstrap.sh. The installer preserves existing settings. Review and test the merged configuration; copying a template does not activate it automatically.
Maintained script: lint-on-edit.sh
Use templates/hooks/lint-on-edit.sh. The old lint-edited.sh example name is retired; its incoming anchor remains. Inspect the maintained script's supported file/path scope and configure equivalent coverage for the adopter. The former inline implementation is retired to avoid a second, divergent executable source.
Design Decisions
Separate advisory feedback from mandatory evidence. Do not hide missing executables or parse failures behind a report of “clean.” A quick production-file check does not establish that test, configuration and script changes were assessed. Comprehensive profile checks cover those separately.
Extending Layer 1 for Frontend
Select the existing project's TypeScript/JavaScript lint and type commands. Use installed, pinned tools rather than an implicit package download. Add fixtures for relevant paths, spaces, malformed events, missing tools and intentional lint failures. No generic frontend adapter is claimed simply because this extension procedure is documented.
Session-end verification reminder
This layer is a proposed reminder point. The shipped adapter only writes a debug-log message; it is not an autonomous approval or completion engine.
settings.json Configuration
Register the installed verify-before-stop.sh as the reference command hook. Review the current settings template and runtime documentation for syntax and timeout support. Preserve unrelated permissions and hooks.
How the Stop Hook Works
When its declared change detection applies, the script prints a verification reminder and exits successfully. The runtime stores this plain output in debug logs, not model context. The engineering task still needs explicit evidence: revision, command, environment, non-empty required selection, outcomes and known gaps. Neither the message nor the hook's successful exit is that evidence.
Reminder limits
No comparative power claim is supported. Timely feedback is a design goal, not a demonstrated property of this adapter. Even a successfully delivered reminder could be ignored and would not establish execution.
Designing Effective Stop Prompts
Keep reminders short and specific to the adopted profile. Point to actual verification commands and evidence expectations. Do not require raw conversation logs or hidden model reasoning. Avoid repeated prompts on discussion-only turns and any loop that pressures the model to invent completion evidence.
Per-Project Stop Hook Variants
Select backend, frontend, documentation or mixed checks according to actual changes and the project's test policy. Local feedback may be targeted; CI/release must provide the comprehensive required evidence. If a coverage threshold is referenced, use the approved value and scope, not an approximate aggregate.
Enhanced Stop Hook — Test Mapping
A future receipt-aware completion service could map changed artifacts to expected tests and verify execution identity/results. The supplied Stop script does not implement this. A mapping file or a test's presence is not proof it ran; omitted and empty selections require explicit handling.
Common Stop Hook Mistakes
- Treating a successful advisory hook as a passed test suite.
- Assuming a prompt forces re-verification.
- Recommending a lint-rule subset as if it matched comprehensive CI.
- Suppressing errors from supposed mandatory checks with
|| true. - Depending on hook ordering that the installed runtime does not guarantee.
Pre-push quality gate
This local adapter blocks a recognized tool call when its configured checks fail or required configuration is absent. Human commands, aliases, nested scripts and other paths need server controls.
settings.json Configuration
Use the installed PreToolUse adapter with the supported shell-tool matcher. Set the actual source root and executable project test command. Test registration using a harmless deliberately failing check; do not push to a real remote merely to test the adapter.
Hook Script: pre-push-gate.sh
The maintained implementation is templates/hooks/pre-push-gate.sh; regressions live in tests/run-checks.sh and tests/test_tooling_safety.py. It requires the event's absolute cwd to resolve to the installed project root. Only a standalone direct git push is supported. Recognized pushes using Git global options, wrappers, assignments or compound commands are blocked rather than checking the wrong repository. Repository-selecting environment overrides also block. The configured source root selects the lint/test subtree, not a different push target. Aliases and nested interpreters still require server controls; this is not a complete shell security parser.
Extended Pre-Push Gate
Put additional profile checks in one maintained project gate with explicit test, lint, type and build scope. Set the PR template's S4U_PROJECT_GATE step environment value to that reviewed repository-relative executable. The template does not create or assume a script: an empty selection or absent/nonexecutable file fails as unconfigured, and a selected gate's failure propagates. Coverage and non-empty expected selections need real runner configuration; do not add successful echo placeholders.
Design Decisions
A mandatory gate cannot authorize a push because its root, command or evidence is unknown. Distinguish assessed failure from inability to assess in the diagnostic while blocking the affected operation. A pre-push parser is a convenience filter, not a security parser for every shell grammar.
Blocking Destructive Operations
Use the runtime sandbox/permission system and repository protections as the actual authority boundaries. An illustrative substring test for force-push or hard-reset is bypassable and is no longer supplied as a security control. Destructive actions still require exact authorized targets, preservation/recovery considerations and a tested operation path.
Subagent Blind Spot
Inheritance and hook support are runtime/version-specific; do not assume either universal inheritance or universal absence. Test the agent mode actually used. A controller must inspect returned artifact identity and required evidence. Prompting a worker to run checks is not a substitute for those results or for server-side enforcement.
Security Scanning Hooks
Use the project's approved scanners and protected CI review. Confirm code/data handling before any external upload. An unavailable or unauthorized scanner remains unassessed; local checks do not automatically provide equivalent assurance. Bound remediation attempts, preserve findings and obtain authority for material policy or scope changes.
Documentation freshness check
templates/hooks/check-doc-staleness.sh <docs-dir> [window-days] reports supported document verification dates and unassessed metadata. The default window is 30 days. It is advisory, read-only and not automatically registered in templates/settings.json: the adopter must choose the actual document scope and verify delivery. Calendar age is not semantic truth or evidence of deployment. See architecture documentation checks.
Memory budget check
templates/hooks/memory-budget-check.sh checks the selected memory hub's byte budget and entry hygiene without rewriting it or exposing entry contents in diagnostics. The settings template registers it on Stop; its zero exit and plain output are advisory/debug-only, not proof of memory loading or a blocker. See memory discipline.
6. Hook Configuration Reference
Complete settings.json Structure
The single executable configuration source is templates/settings.json, not a second copy embedded here. The current template uses reference hook paths and must be reviewed against the adopter's existing settings. Installation status, configuration status and runtime verification are separate records.
Configuration Fields
Validate supported event, matcher, hook type, executable command and timeout fields against the installed runtime. Reject incompatible configuration rather than assuming a field was honored. Do not carry historical claims that prompt hooks support only one event or that every runtime exposes exactly three events.
Hook File Placement
The reference installer places scripts under .claude/hooks/ and leaves edited files intact, offering upgrade candidates. Resolve execution root explicitly, preserve executable mode and test paths containing spaces. Do not overwrite an existing configuration to make a sample match.
setup/bootstrap.sh uses its adjacent setup/bootstrap.py (Python 3.9+ standard library). Run the entry point from a complete, pinned kit checkout; copying the shell wrapper alone is not an installation. Dry-run previews selected writes without creating a lock or files. The installer preserves existing CLAUDE.md, settings, local bytes and POSIX modes; its legacy checksum receipt does not establish ownership of permissions. Edited upgrade proposals receive another numbered candidate instead of being overwritten. Supported selected inputs are regular, single-link files; symlinks, special files and malformed ownership receipts require explicit resolution.
Text equivalent: planning and backups precede installation. Precommit failure or a handled interruption restores prior bytes and modes where safe. A conflicting edit is preserved and recovery requires review. Once committed, cleanup failure is a different outcome: the installed version remains new and must not be rolled back from incomplete backups. This is process-level recovery for controlled local directories, not atomic multi-file visibility. Quiesce consumers and other writers during upgrade. ACLs, extended attributes, file ownership, machine failure and hostile-filesystem containment are outside the stated guarantee.
A remaining project-root .s4u-install.lock refuses retry. First establish whether the installer is still running. If stopped, inspect journal.json, the actual installed files and any payloads/*.original/*.proposed entries; journal state alone after an abrupt machine/process failure is insufficient. Reconcile only listed paths and preserve intervening edits. For committed or rolled_back with incomplete cleanup, confirm the corresponding installed state before removing the residual installer-owned data. Do not blindly delete a lock or use a recursive project-root cleanup. The older .claude/.s4u-install.lock is also refused by a real installation and needs separate inspection.
Multiple Hook Groups
Do not rely on undocumented sequential execution for dependent checks. If ordering is essential, invoke them through one checked orchestrating command with explicit failure propagation. Prevent overlapping writes and shared predictable temporary paths.
7. Integration with Superpowers
Skills guide procedure. Hooks assess a configured subset or provide reminders. Neither supplies business authority or guarantees correct outcomes.
The Verification Overlap
| Mechanism | Contribution | Limit |
|---|---|---|
| Verification skill | Instructs the agent to gather and report evidence | Instruction is not execution |
| Stop reminder | Prompts attention to missing verification | No receipt collection or enforced re-run |
| Test/build gate | Executes declared checks | Result is limited to selection and oracle |
| Protected review/release | Applies authorized decisions to exact artifacts | Requires verified configuration and accountable owners |
Lifecycle Integration Points
Text equivalent: the lifecycle produces checked artifacts and a receipt; hooks support the work but cannot replace independent review or release authority.
When Skills and Hooks Disagree
Actual output can contradict an unverified claim, but a hook does not outrank binding policy. Resolve differences against approved authority and applicable controls; preserve evidence of implementation drift. A reminder that mentions tests is not evidence that tests are absent or present.
8. Creating Custom Hooks
Step-by-Step Guide
- Define the risk, applicable subject set, owner and expected evidence.
- Select a supported runtime event and document what it cannot observe.
- Reproduce the failure in a disposable fixture before implementing the hook.
- Validate input and configuration; use argument arrays/quoting rather than injected shell source.
- Register the adapter without overwriting unrelated settings.
- Test known pass, known fail, missing dependency, malformed input, empty selection and timeout/cancellation.
- Verify actual host invocation and blocking semantics separately from script exit status.
- Record cost, limitations, installation version and retirement criteria.
Best Practices
Fail-Open vs. Fail-Closed
Advisory feedback can leave editing available while clearly reporting an unassessed check. Mandatory authorization or release gates block on absent configuration, inaccessible evidence and execution failure. For the Claude PreToolUse adapter, blocking is exit 2; ordinary shell-script exit conventions are not interchangeable with host hook conventions.
Timeout Handling
Measure actual latency and choose proportionate local versus CI cadence. Cancellation must not leave a mutated fixture, concurrent writer or lost original artifact. When required work times out, record unassessed/failure rather than treating a background process as completed.
Error Messages
Name the check, subject, category and safe next step. Never echo secrets, credential-bearing URLs, private payloads or complete arbitrary commands merely to make a diagnostic detailed. Preserve sanitized evidence under the appropriate access policy.
Idempotency
Read-only checks should not modify the assessed subject. When a generator or test intentionally writes, use an owned isolated location, checked restoration and explicit concurrency handling. Repeated activation or external dispatch needs its own idempotency protocol, not just a repeatable hook.
Example: Custom Hook for Migration Safety
The old staged-file/table-name grep did not establish migration safety: pushed changes are often already committed, and migration-only DDL need not have ORM counterparts. Assess the exact PR/push revision range. Missing history is unassessed, not an empty change set.
Apply the real migration chain to a disposable database, inspect expected constraints/indexes and runtime privileges, test upgrade compatibility and intended data transformations, and rehearse the applicable recovery strategy. Not every destructive migration has a safe symmetric downgrade. Preserve privileged migration roles separately from application identities. A project must implement and verify these checks; this paragraph does not claim a shipped migration-safety gate.
9. Troubleshooting
Hook Does Not Fire
Check installed runtime version, event/matcher, effective settings source, executable path and mode, and whether that actor/tool mode emits the expected event. Use a harmless disposable probe. Do not broaden permissions or real-world effects to diagnose a missing hook.
Hook Fires But Does Not Block
Distinguish a script error from a host-recognized denial. Verify the documented exit/output contract and inspect whether the attempted operation actually occurred. For this PreToolUse adapter, exit 2 blocks; exit 1 does not establish denial.
Hook Is Too Slow
Measure the command with a portable timer, examine subject scope and split targeted local feedback from comprehensive CI. Do not reduce required checks silently. A background run remains pending until a matching completion receipt is available.
Stop Hook Does Not Trigger Re-Verification
That is not a capability of the supplied advisory adapter. Obtain the missing evidence explicitly and keep incomplete work labelled incomplete. Stronger wording does not turn a reminder into a deterministic completion gate.
Summary
Use one maintained adapter implementation, test the installed host integration, and report actual evidence scope. Local convenience, server enforcement, accountable approval and production acceptance are different controls. Keep all four visible.